From 6dd81d027718bfcd888326369fa5f67057e949a8 Mon Sep 17 00:00:00 2001 From: Chaitali Mane <71449322+chaitali-mane@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:09:28 +0530 Subject: [PATCH] Added org user API integration (#6817) * Added APi integration Signed-off-by: Chaitali Mane * Added code integration Signed-off-by: Chaitali Mane * Added cypress test case Signed-off-by: Chaitali Mane --- components/automate-ui/src/app/app.module.ts | 2 + .../entities/org-users/org-users.action.ts | 38 ++++++ .../entities/org-users/org-users.effects.ts | 46 +++++++ .../app/entities/org-users/org-users.model.ts | 14 ++ .../entities/org-users/org-users.reducer.ts | 49 +++++++ .../entities/org-users/org-users.requests.ts | 17 +++ .../entities/org-users/org-users.selectors.ts | 18 +++ .../org-users/org-users.component.html | 15 ++- .../org-users/org-users.component.ts | 53 ++++++-- .../automate-ui/src/app/ngrx.effects.ts | 2 + .../automate-ui/src/app/ngrx.reducers.ts | 4 + .../ui/infra-proxy/infra-orgUsers.spec.ts | 122 ++++++++++++++++++ 12 files changed, 366 insertions(+), 14 deletions(-) create mode 100644 components/automate-ui/src/app/entities/org-users/org-users.action.ts create mode 100644 components/automate-ui/src/app/entities/org-users/org-users.effects.ts create mode 100644 components/automate-ui/src/app/entities/org-users/org-users.model.ts create mode 100644 components/automate-ui/src/app/entities/org-users/org-users.reducer.ts create mode 100644 components/automate-ui/src/app/entities/org-users/org-users.requests.ts create mode 100644 components/automate-ui/src/app/entities/org-users/org-users.selectors.ts create mode 100644 e2e/cypress/integration/ui/infra-proxy/infra-orgUsers.spec.ts diff --git a/components/automate-ui/src/app/app.module.ts b/components/automate-ui/src/app/app.module.ts index 35eda5f8269..7db9c7103fa 100644 --- a/components/automate-ui/src/app/app.module.ts +++ b/components/automate-ui/src/app/app.module.ts @@ -106,6 +106,7 @@ import { RunlistRequests } from './entities/runlists/runlists.requests'; import { ServerRequests } from './entities/servers/server.requests'; import { NodeCredentialRequests } from './entities/node-credentials/node-credential.requests'; import { OrgRequests } from './entities/orgs/org.requests'; +import { OrgUserRequests } from './entities/org-users/org-users.requests'; import { PolicyFileRequests } from './entities/policy-files/policy-file.requests'; import { ServiceGroupsRequests } from './entities/service-groups/service-groups.requests'; import { TeamRequests } from './entities/teams/team.requests'; @@ -384,6 +385,7 @@ import { ServerRequests, NodeCredentialRequests, OrgRequests, + OrgUserRequests, PolicyFileRequests, ServiceGroupsRequests, SessionStorageService, diff --git a/components/automate-ui/src/app/entities/org-users/org-users.action.ts b/components/automate-ui/src/app/entities/org-users/org-users.action.ts new file mode 100644 index 00000000000..00ef4cf3387 --- /dev/null +++ b/components/automate-ui/src/app/entities/org-users/org-users.action.ts @@ -0,0 +1,38 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Action } from '@ngrx/store'; +import { OrgUser } from './org-users.model'; + +export enum OrgUsersActionTypes { + GET_ALL = 'USERS::GET_ALL', + GET_ALL_SUCCESS = 'USERS::GET_ALL::SUCCESS', + GET_ALL_FAILURE = 'USERS::GET_ALL::FAILURE' +} + +export interface UsersSuccessPayload { + orgUsers: OrgUser[]; +} + +export interface UsersPayload { + org_id: string; + server_id: string; +} + +export class GetUsers implements Action { + readonly type = OrgUsersActionTypes.GET_ALL; + constructor(public payload: UsersPayload) { } +} + +export class GetUsersSuccess implements Action { + readonly type = OrgUsersActionTypes.GET_ALL_SUCCESS; + constructor(public payload: UsersSuccessPayload) { } +} + +export class GetUsersFailure implements Action { + readonly type = OrgUsersActionTypes.GET_ALL_FAILURE; + constructor(public payload: HttpErrorResponse) { } +} + +export type OrgUsersActions = + | GetUsers + | GetUsersSuccess + | GetUsersFailure; diff --git a/components/automate-ui/src/app/entities/org-users/org-users.effects.ts b/components/automate-ui/src/app/entities/org-users/org-users.effects.ts new file mode 100644 index 00000000000..38992ef3fee --- /dev/null +++ b/components/automate-ui/src/app/entities/org-users/org-users.effects.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { of as observableOf } from 'rxjs'; +import { catchError, mergeMap, map } from 'rxjs/operators'; +import { CreateNotification } from 'app/entities/notifications/notification.actions'; +import { Type } from 'app/entities/notifications/notification.model'; + +import { + GetUsers, + GetUsersSuccess, + GetUsersFailure, + UsersSuccessPayload, + OrgUsersActionTypes +} from './org-users.action'; + +import { OrgUserRequests } from './org-users.requests'; + +@Injectable() +export class OrgUserEffects { + constructor( + private actions$: Actions, + private requests: OrgUserRequests + ) { } + + getUsers$ = createEffect(() => + this.actions$.pipe( + ofType(OrgUsersActionTypes.GET_ALL), + mergeMap(({ payload: { server_id, org_id } }: GetUsers) => + this.requests.OrgUserRequests(server_id, org_id).pipe( + map((resp: UsersSuccessPayload) => new GetUsersSuccess(resp)), + catchError((error: HttpErrorResponse) => + observableOf(new GetUsersFailure(error))) + )))); + + getUsersFailure$ = createEffect(() => + this.actions$.pipe( + ofType(OrgUsersActionTypes.GET_ALL_FAILURE), + map(({ payload }: GetUsersFailure) => { + const msg = payload.error.error; + return new CreateNotification({ + type: Type.error, + message: `Could not get clients: ${msg || payload.error}` + }); + }))); +} diff --git a/components/automate-ui/src/app/entities/org-users/org-users.model.ts b/components/automate-ui/src/app/entities/org-users/org-users.model.ts new file mode 100644 index 00000000000..81c524d4907 --- /dev/null +++ b/components/automate-ui/src/app/entities/org-users/org-users.model.ts @@ -0,0 +1,14 @@ +export interface OrgUser { + user_id: string; + server_id: string; + org_id: string; + infra_server_username: string; + first_name: string; + last_name: string; + email_id: string; + middle_name: string; + display_name: string; + connector: string; + automate_user_id: string; + is_admin: boolean; +} diff --git a/components/automate-ui/src/app/entities/org-users/org-users.reducer.ts b/components/automate-ui/src/app/entities/org-users/org-users.reducer.ts new file mode 100644 index 00000000000..d175e59d3ce --- /dev/null +++ b/components/automate-ui/src/app/entities/org-users/org-users.reducer.ts @@ -0,0 +1,49 @@ +import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; +import { set, pipe } from 'lodash/fp'; +import { EntityStatus } from 'app/entities/entities'; +import { OrgUsersActionTypes, OrgUsersActions } from './org-users.action'; +import { OrgUser } from './org-users.model'; + +export interface OrgUserEntityState extends EntityState { + getAllStatus: EntityStatus; + orgUserList: { + orgUsers: OrgUser[] + }; +} + +const GET_ALL_STATUS = 'getAllStatus'; + +export const orgUserEntityAdapter: EntityAdapter = + createEntityAdapter({ + selectId: (orgUser: OrgUser) => orgUser.user_id +}); + +export const OrgUserEntityInitialState: OrgUserEntityState = + orgUserEntityAdapter.getInitialState({ + getAllStatus: EntityStatus.notLoaded + }); + +export function orgUserEntityReducer( + state: OrgUserEntityState = OrgUserEntityInitialState, + action: OrgUsersActions): OrgUserEntityState { + + switch (action.type) { + case OrgUsersActionTypes.GET_ALL: + return set(GET_ALL_STATUS, EntityStatus.loading, orgUserEntityAdapter.removeAll(state)); + + case OrgUsersActionTypes.GET_ALL_SUCCESS: + return pipe( + set(GET_ALL_STATUS, EntityStatus.loadingSuccess), + set('orgUserList.items', action.payload.orgUsers || []) + )(state) as OrgUserEntityState; + + case OrgUsersActionTypes.GET_ALL_FAILURE: + return set(GET_ALL_STATUS, EntityStatus.loadingFailure, state); + + default: + return state; + } +} + +export const getEntityById = (id: string) => + (state: OrgUserEntityState) => state.entities[id]; diff --git a/components/automate-ui/src/app/entities/org-users/org-users.requests.ts b/components/automate-ui/src/app/entities/org-users/org-users.requests.ts new file mode 100644 index 00000000000..10950fc06e3 --- /dev/null +++ b/components/automate-ui/src/app/entities/org-users/org-users.requests.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment as env } from 'environments/environment'; +import { UsersSuccessPayload } from './org-users.action'; + +@Injectable() +export class OrgUserRequests { + + constructor(private http: HttpClient) { } + + public OrgUserRequests(server_id: string, org_id: string) + : Observable { + const url = `${env.infra_proxy_url}/servers/${server_id}/orgs/${org_id}/automateinfraorgusers`; + return this.http.get(url); + } +} diff --git a/components/automate-ui/src/app/entities/org-users/org-users.selectors.ts b/components/automate-ui/src/app/entities/org-users/org-users.selectors.ts new file mode 100644 index 00000000000..503929bfc68 --- /dev/null +++ b/components/automate-ui/src/app/entities/org-users/org-users.selectors.ts @@ -0,0 +1,18 @@ +import { createSelector, createFeatureSelector } from '@ngrx/store'; +import { OrgUserEntityState, orgUserEntityAdapter } from './org-users.reducer'; + +export const orgUserState = createFeatureSelector('OrgUsers'); +export const { + selectAll: allUsers, + selectEntities: userEntities +} = orgUserEntityAdapter.getSelectors(orgUserState); + +export const getAllStatus = createSelector( + orgUserState, + (state) => state.getAllStatus +); + +export const orgUserList = createSelector( + orgUserState, + (state) => state.orgUserList +); diff --git a/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.html b/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.html index efc02bda6b1..9bae80f8daa 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.html +++ b/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.html @@ -8,7 +8,10 @@ - +
+ No preview +
+ Chef Server User Name @@ -18,13 +21,13 @@ - - User Name - Automate Name - automatename@email.com + + {{user.infra_server_username}} + {{user.automate_user_id}} + {{user.email_id}} - Reset PEM Key + Reset User Key diff --git a/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.ts b/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.ts index a4b230d0148..3b1feade8fb 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.ts +++ b/components/automate-ui/src/app/modules/infra-proxy/org-users/org-users.component.ts @@ -1,6 +1,15 @@ import { Component, Input, OnInit, OnDestroy, EventEmitter, Output } from '@angular/core'; -import { Subject } from 'rxjs'; import { LayoutFacadeService, Sidebar } from 'app/entities/layout/layout.facade'; +import { OrgUser } from '../../../entities/org-users/org-users.model'; +import { GetUsers } from '../../../entities/org-users/org-users.action'; +import { getAllStatus, orgUserList } from '../../../entities/org-users/org-users.selectors'; +import { Store } from '@ngrx/store'; + +import { combineLatest, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { isNil } from 'lodash/fp'; +import { NgrxStateAtom } from 'app/ngrx.reducers'; +import { EntityStatus } from 'app/entities/entities'; @Component({ selector: 'app-org-users', @@ -13,22 +22,41 @@ export class OrgUsersComponent implements OnInit, OnDestroy { @Input() orgId: string; @Output() resetKeyRedirection = new EventEmitter(); - //Will variable add type at time of API integration - public users; - public usersListLoading: boolean = false; - public authFailure: boolean = false; - public loading: boolean = false; - public current_page:number = 1; - public per_page:number = 100; + public usersState: { orgUsers: OrgUser[] }; + public users: OrgUser[] = []; + public usersListLoading = false; + public authFailure = false; + public loading = false; + public current_page = 1; + public per_page = 100; public total: number; private isDestroyed = new Subject(); constructor( + private store: Store, private layoutFacade: LayoutFacadeService ) {} ngOnInit(): void { this.layoutFacade.showSidebar(Sidebar.Infrastructure); + + this.getOrgUsersData(); + + combineLatest([ + this.store.select(getAllStatus), + this.store.select(orgUserList) + ]).pipe(takeUntil(this.isDestroyed)) + .subscribe(([getUsersSt, OrgUsersState]) => { + if (getUsersSt === EntityStatus.loadingSuccess && !isNil(OrgUsersState)) { + this.usersState = OrgUsersState; + this.users = this.usersState.orgUsers; + this.usersListLoading = false; + this.loading = false; + } else if (getUsersSt === EntityStatus.loadingFailure) { + this.usersListLoading = false; + this.authFailure = true; + } + }); } ngOnDestroy(): void { @@ -40,6 +68,15 @@ export class OrgUsersComponent implements OnInit, OnDestroy { this.resetKeyRedirection.emit(resetLink); } + getOrgUsersData(): void { + const payload = { + server_id: this.serverId, + org_id: this.orgId + }; + + this.store.dispatch(new GetUsers(payload)); + } + onPageChange(event: number): void { this.current_page = event; this.loading = true; diff --git a/components/automate-ui/src/app/ngrx.effects.ts b/components/automate-ui/src/app/ngrx.effects.ts index 150dc0835e0..e198054bebf 100644 --- a/components/automate-ui/src/app/ngrx.effects.ts +++ b/components/automate-ui/src/app/ngrx.effects.ts @@ -33,6 +33,7 @@ import { NodeCredentialEffects } from './entities/node-credentials/node-credenti import { NodeRunlistEffects } from './entities/nodeRunlists/nodeRunlists.effects'; import { NotificationRuleEffects } from './entities/notification_rules/notification_rule.effects'; import { OrgEffects } from './entities/orgs/org.effects'; +import { OrgUserEffects } from './entities/org-users/org-users.effects'; import { PolicyEffects } from './entities/policies/policy.effects'; import { PolicyFileEffects } from './entities/policy-files/policy-file.effects'; import { ProfileEffects } from './entities/profiles/profile.effects'; @@ -84,6 +85,7 @@ import { UserPreferencesEffects } from './services/user-preferences/user-prefere NodeRunlistEffects, NotificationRuleEffects, OrgEffects, + OrgUserEffects, PolicyEffects, PolicyFileEffects, ProfileEffects, diff --git a/components/automate-ui/src/app/ngrx.reducers.ts b/components/automate-ui/src/app/ngrx.reducers.ts index 4f28731bec0..a5ffdc06073 100644 --- a/components/automate-ui/src/app/ngrx.reducers.ts +++ b/components/automate-ui/src/app/ngrx.reducers.ts @@ -43,6 +43,7 @@ import * as nodeRunlistEntity from './entities/nodeRunlists/nodeRunlists.reducer import * as notificationEntity from './entities/notifications/notification.reducer'; import * as NotificationRuleEntity from './entities/notification_rules/notification_rule.reducer'; import * as orgEntity from './entities/orgs/org.reducer'; +import * as OrgUserEntity from './entities/org-users/org-users.reducer'; import * as permEntity from './entities/userperms/userperms.reducer'; import * as policyEntity from './entities/policies/policy.reducer'; import * as policyFileEntity from './entities/policy-files/policy-file.reducer'; @@ -121,6 +122,7 @@ export interface NgrxStateAtom { notifications: notificationEntity.NotificationEntityState; notificationRules: NotificationRuleEntity.NotificationRuleEntityState; orgs: orgEntity.OrgEntityState; + OrgUsers: OrgUserEntity.OrgUserEntityState; policies: policyEntity.PolicyEntityState; policyFiles: policyFileEntity.PolicyFileEntityState; policyFileDetails: policyFileDetailsEntity.PolicyFileDetailsEntityState; @@ -274,6 +276,7 @@ export const defaultInitialState = { nodeCredentialDetails: nodeCredentialDetailsEntity.NodeCredentialEntityInitialState, servers: serverEntity.ServerEntityInitialState, orgs: orgEntity.OrgEntityInitialState, + OrgUsers: OrgUserEntity.OrgUserEntityInitialState, serviceGroups: serviceGroups.ServiceGroupEntityInitialState, teams: teamEntity.TeamEntityInitialState, desktops: desktopEntity.desktopEntityInitialState, @@ -346,6 +349,7 @@ export const ngrxReducers = { runlist: runlistEntity.runlistEntityReducer, servers: serverEntity.serverEntityReducer, orgs: orgEntity.orgEntityReducer, + OrgUsers: OrgUserEntity.orgUserEntityReducer, nodeCredential: nodeCredentialEntity.nodeCredentialEntityReducer, nodeCredentialDetails: nodeCredentialDetailsEntity.nodeCredentialDetailsEntityReducer, serviceGroups: serviceGroups.serviceGroupsEntityReducer, diff --git a/e2e/cypress/integration/ui/infra-proxy/infra-orgUsers.spec.ts b/e2e/cypress/integration/ui/infra-proxy/infra-orgUsers.spec.ts new file mode 100644 index 00000000000..dcacb5f5de5 --- /dev/null +++ b/e2e/cypress/integration/ui/infra-proxy/infra-orgUsers.spec.ts @@ -0,0 +1,122 @@ +describe('infra databag', () => { + const now = Cypress.moment().format('MMDDYYhhmmss'); + const cypressPrefix = 'infra'; + let adminIdToken = ''; + const serverID = 'chef-manage'; + const serverName = 'chef manage'; + const orgID = 'demoorg'; + const orgName = 'demoorg'; + const serverFQDN = Cypress.env('AUTOMATE_INFRA_SERVER_FQDN'); + const adminUser = 'kallol'; + const adminKey = Cypress.env('AUTOMATE_INFRA_ADMIN_KEY').replace(/\\n/g, '\n'); + const webuiKey = Cypress.env('AUTOMATE_INFRA_WEBUI_KEY').replace(/\\n/g, '\n'); + + before(() => { + cy.adminLogin('/').then(() => { + const admin = JSON.parse(localStorage.getItem('chef-automate-user')); + adminIdToken = admin.id_token; + + cy.request({ + auth: { bearer: adminIdToken }, + failOnStatusCode: false, + method: 'POST', + url: '/api/v0/infra/servers', + body: { + id: serverID, + name: serverName, + fqdn: serverFQDN, + ip_address: '', + webui_key: webuiKey + } + }).then((resp) => { + if (resp.status === 200 && resp.body.ok === true) { + return; + } else { + cy.request({ + auth: { bearer: adminIdToken }, + method: 'GET', + url: `/api/v0/infra/servers/${serverID}`, + body: { + id: serverID + } + }); + } + }); + + cy.request({ + auth: { bearer: adminIdToken }, + failOnStatusCode: false, + method: 'POST', + url: `/api/v0/infra/servers/${serverID}/orgs`, + body: { + id: orgID, + server_id: serverID, + name: orgName, + admin_user: adminUser, + admin_key: adminKey + } + }).then((response) => { + if (response.status === 200 && response.body.ok === true) { + return; + } else { + cy.request({ + auth: { bearer: adminIdToken }, + method: 'GET', + url: `/api/v0/infra/servers/${serverID}/orgs/${orgID}`, + body: { + id: orgID, + server_id: serverID + } + }); + } + }); + cy.visit(`/infrastructure/chef-servers/${serverID}/organizations/${orgID}`); + cy.get('app-welcome-modal').invoke('hide'); + }); + cy.restoreStorage(); + }); + + beforeEach(() => { + cy.restoreStorage(); + }); + + afterEach(() => { + cy.saveStorage(); + }); + + function getOrgUsers() { + return cy.request({ + auth: { bearer: adminIdToken }, + failOnStatusCode: false, + method: 'GET', + url: `/api/v0/infra/servers/${serverID}/orgs/${orgID}/automateinfraorgusers` + }); + } + + function checkResponse(response: any) { + if (response.body.users === 0) { + cy.get('[data-cy=empty-list]').should('be.visible'); + } else { + cy.get('[data-cy=orgUsers-table-container] chef-th').contains('Name'); + return true; + } + } + + describe('infra organization users list page', () => { + it('displays org details', () => { + cy.get('.page-title').contains(orgName); + }); + + // organization users tabs specs + it('can switch to organization users tab', () => { + cy.get('.nav-tab').contains('Users').click(); + }); + + it('can check if organization users has list or not', () => { + getOrgUsers().then((response) => { + checkResponse(response); + }); + }); + + }); +});