From 21a9f84956306ca5ddcdcc12c5c848ad4af60cd0 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Mon, 21 Nov 2022 08:40:27 -0800 Subject: [PATCH] [EC-16] Implement new Groups Tab (#3563) * [EC-16] Cleanup RxJS linting problems * [EC-16] Update Group tab to use table component and show collections. * [EC-16] Extract interface from GroupResponse and use it in the view * [EC-16] Remove heading underline * [EC-16] Cleanup i18n * [EC-16] More i18n cleanup * [EC-16] Fix bulk group request type name * [EC-16] Rename group details type * [EC-86] Clear collectionMap before populating it with new collections * [EC-86] Update initialization/loading logic to make better use of the Observable pattern * [EC-86] Make table cells use a pointer cursor * [EC-86] Use bitIconButton for row menu triggers * [EC-86] Refactor GroupDetailsRow interface to wrap GroupDetailsResponse. Remove response model interfaces. Cleanup GroupsComponent. * [EC-86] Add bit-badge-list component and tweak BadgeModule to support both the component and directive. Update mockI18nService to support templated strings. * [EC-86] Cleanup badge color and bitIconButton classes * [EC-86] Cleanup more styles * [EC-86] Add GroupApiService Add a new GroupApiService to replace Group Api calls in the ApiService. * [EC-86] Revisions for badge-list implementation. - Remove `| null` for maxItems according to ADR-0014 - Remove custom setter for items - Use ngOnChanges to update filteredItems - Fix sr-only tailwind class and show screen reader comma after last item if truncated. * [EC-86] Refactor badge-list module/component - Move the badge list component to its own module. - Extract badge list stories from badge stories. - Cleanup bade stories and module after refactor. * [EC-86] Refactor/rename GroupApiService - Re-name GroupApiService to GroupService as there is no need for a separate Api service (no sync or local data for admin services) - Add GroupView for use in the GroupService instead of raw API models - Update views to use GroupView instead of raw GroupResponse models * [EC-86] Refactor group API request models - Move organizationGroupBulkRequest to group requests folder - Fix relative imports in GroupService * [EC-86] Fix linting errors * Fix tab item text color Tab item text color broke after a merge from master and needs a fix to account for bootstrap styles in Web. * [EC-86] Rename new files using kebab-case * [EC-86] Fix group view file name * [EC-86] Fix group request/response file names * [EC-86] Cleanup badge stories per review suggestions * [EC-86] Use inline-flex for badge list container * [EC-86] Move GroupService and Views to Web org module - Move GroupService and GroupServiceAbstraction to Organization Module - Add GroupService provider to Organization Module - Move collection-add-edit.component, user-groups.component, group-add-edit.component, and groups.component into Organization Module as they now depend on GroupService - Remove moved components from Loose Component module * [EC-86] Fix Group table search Adds the id and name properties to GroupDetailsRow to support using the searchPipe (which cannot access nested values such as details.name for filtering). * [EC-86] Fix badge story controls * [EC-87] Edit Group Dialog (#3651) * [EC-87] Update the edit dialog to use content tabs * [EC-87] WIP FormListSelection abstract controller * [EC-87] WIP FormListSelection for members and collections * [EC-87] More WIP on FormListSelection * [EC-87] WIP Working FormSelectionList with initial value support * [EC-87] WIP SelectionList without FormControls and with i18n support for sorting * [EC-87] Final sorted SelectionList with FormArray support * [EC-87] Extract and document FormSelectionList * [EC-87] Functional edit group modal * [EC-87] Remove button icon padding for bitButton directives * [EC-87] Use new disablePadding attribute for Dialog component * [EC-87] Some more cleanup and finetuning * [EC-87] Move enum declaration to top * [EC-87] Remove inline style from access selector * [EC-87] Move Group components into Organization Module * [EC-87] Add MultiSelectModule to Shared Web module * [EC-87] Integrate AccessSelector component in GroupAddEdit modal - Remove duplicate permission / selection readonly helpers from GroupAddEdit component - Use access item views/values for collection and member lists - Replace access selector HTMl with the AccessSelector component * [EC-87] Update Group collections column to open Collection tab * [EC-87] Remove old FormSelectionList file * [EC-87] Fix missed file import changes after merge * [EC-87] Remove GroupAddEditComponent modal service registration Groups component is now using the DialogService which does not require explicit registration for lazy loaded components. * [EC-87] Use injected DIALOG_DATA for GroupAddEdit component - Add types for the GroupAddEdit dialog params, result, and tab indices - Add strongly typed helper method to open GroupAddEdit dialogs - Remove @Input()/@Output() properties. Replaced with the injected DIALOG_DATA params instead - Use dialogRef.close() and result type instead of event emitters * [EC-87] Rename collection tab type to collections * [EC-87] Refactor postGroup() and putGroup() from ApiService - Move postGroup() and putGroup() methods to GroupService - Remove postGroup() and putGroup() from ApiService - Move GroupResponse and GroupRequest into Web (from lib/common) * [EC-87] Remove required attribute * [EC-87] Use PascalCase for template Enums * [EC-87] Use group modal tab enum in template * [EC-87] Convert dialog result to promise * [EC-87] Refactor dialog positionStrategy - Add .top() to position strategy to allow clicking the backdrop to close the dialog - Move the positionStrategy option into the openGroupAddEditDialog helper * [EC-87] Remove [preserveContent] from tab group * [EC-87] Use new CL async actions - Update handlers to be arrow-functions - Remove old form and delete promises - Use [bitSubmit] directive on form - Use bitFormButton directive and [bitAction] for submit and delete buttons - Remove delete/spinner bwi icons as they are handled by the new async directives * [EC-87] Introduce CollectionAccessSelectionView Use a new view to replace the SelectionReadonlyResponse/Request classes. * [EC-87] Use new access selection view in GroupView - Change the collections type - Add members list to make the view more complete - Update the static fromResponse helper to properly map the GroupDetailsResponse to the new access selection view - Update access selector helpers to use new access selection view instead of response/request models * [EC-87] Update GroupService to have a single save() method that accepts a GroupView - Add save() method that checks for existing group id to determine which API method to use - Make post/put group methods private * [EC-87] Utilize the new save() method in the group modal * [EC-87] Use observables for fetching data - Introduce 3 observables for collections, members, and group details - Combine and subscribe to those observables in ngOnInit - Add destroy$ subject - Inject changeDetectorRef to handle quirk of patching the AccessSelector value before available items are set --- .../access-selector.component.html | 2 +- .../access-selector/access-selector.models.ts | 22 +- .../manage/collection-add-edit.component.ts | 15 +- .../manage/group-add-edit.component.html | 261 +++++---------- .../manage/group-add-edit.component.ts | 314 +++++++++++++----- .../manage/groups.component.html | 150 ++++++--- .../organizations/manage/groups.component.ts | 305 +++++++++++++---- .../manage/user-groups.component.ts | 12 +- .../app/organizations/organization.module.ts | 21 ++ .../group/group.service.abstraction.ts | 11 + .../services/abstractions/group/index.ts | 2 + .../group/requests}/group.request.ts | 3 +- .../services/group/group.service.ts | 110 ++++++ .../organization-group-bulk.request.ts | 7 + .../group/responses}/group.response.ts | 4 +- .../views/collection-access-selection.view.ts | 24 ++ .../src/app/organizations/views/group.view.ts | 24 ++ .../src/app/shared/loose-components.module.ts | 12 - apps/web/src/app/shared/shared.module.ts | 14 +- apps/web/src/locales/en/messages.json | 65 +++- apps/web/src/scss/base.scss | 2 +- libs/angular/src/jslib.module.ts | 5 +- libs/angular/src/pipes/user-type.pipe.ts | 29 ++ .../src/services/jslib-services.module.ts | 14 +- libs/common/src/abstractions/api.service.ts | 13 +- libs/common/src/services/api.service.ts | 64 +--- .../src/badge-list/badge-list.component.html | 9 + .../src/badge-list/badge-list.component.ts | 35 ++ .../src/badge-list/badge-list.module.ts | 13 + .../src/badge-list/badge-list.stories.ts | 53 +++ libs/components/src/badge-list/index.ts | 1 + libs/components/src/badge/badge.directive.ts | 2 +- libs/components/src/badge/badge.stories.ts | 9 +- libs/components/src/badge/index.ts | 2 +- libs/components/src/dialog/dialog.module.ts | 2 +- libs/components/src/index.ts | 1 + .../components/src/utils/i18n-mock.service.ts | 8 +- 37 files changed, 1129 insertions(+), 511 deletions(-) create mode 100644 apps/web/src/app/organizations/services/abstractions/group/group.service.abstraction.ts create mode 100644 apps/web/src/app/organizations/services/abstractions/group/index.ts rename {libs/common/src/models/request => apps/web/src/app/organizations/services/abstractions/group/requests}/group.request.ts (51%) create mode 100644 apps/web/src/app/organizations/services/group/group.service.ts create mode 100644 apps/web/src/app/organizations/services/group/requests/organization-group-bulk.request.ts rename {libs/common/src/models/response => apps/web/src/app/organizations/services/group/responses}/group.response.ts (81%) create mode 100644 apps/web/src/app/organizations/views/collection-access-selection.view.ts create mode 100644 apps/web/src/app/organizations/views/group.view.ts create mode 100644 libs/angular/src/pipes/user-type.pipe.ts create mode 100644 libs/components/src/badge-list/badge-list.component.html create mode 100644 libs/components/src/badge-list/badge-list.component.ts create mode 100644 libs/components/src/badge-list/badge-list.module.ts create mode 100644 libs/components/src/badge-list/badge-list.stories.ts create mode 100644 libs/components/src/badge-list/index.ts diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.component.html b/apps/web/src/app/organizations/components/access-selector/access-selector.component.html index 10390b3e053..f695a9d212b 100644 --- a/apps/web/src/app/organizations/components/access-selector/access-selector.component.html +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.component.html @@ -37,7 +37,7 @@ {{ "role" | i18n }} {{ "group" | i18n }} - + diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.models.ts b/apps/web/src/app/organizations/components/access-selector/access-selector.models.ts index d621de271e0..5a9f878e55a 100644 --- a/apps/web/src/app/organizations/components/access-selector/access-selector.models.ts +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.models.ts @@ -1,9 +1,9 @@ import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; -import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { SelectionReadOnlyResponse } from "@bitwarden/common/models/response/selection-read-only.response"; import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view"; +import { CollectionAccessSelectionView } from "../../views/collection-access-selection.view"; + /** * Permission options that replace/correspond with readOnly and hidePassword server fields. */ @@ -75,11 +75,11 @@ export type AccessItemValue = { }; /** - * Converts the older SelectionReadOnly interface to one of the new CollectionPermission values + * Converts the CollectionAccessSelectionView interface to one of the new CollectionPermission values * for the dropdown in the AccessSelectorComponent * @param value */ -export const convertToPermission = (value: SelectionReadOnlyResponse) => { +export const convertToPermission = (value: CollectionAccessSelectionView) => { if (value.readOnly) { return value.hidePasswords ? CollectionPermission.ViewExceptPass : CollectionPermission.View; } else { @@ -88,16 +88,16 @@ export const convertToPermission = (value: SelectionReadOnlyResponse) => { }; /** - * Converts an AccessItemValue back into a SelectionReadOnly class using the CollectionPermission + * Converts an AccessItemValue back into a CollectionAccessView class using the CollectionPermission * to determine the values for `readOnly` and `hidePassword` * @param value */ -export const convertToSelectionReadOnly = (value: AccessItemValue) => { - return new SelectionReadOnlyRequest( - value.id, - readOnly(value.permission), - hidePassword(value.permission) - ); +export const convertToSelectionView = (value: AccessItemValue) => { + return new CollectionAccessSelectionView({ + id: value.id, + readOnly: readOnly(value.permission), + hidePasswords: hidePassword(value.permission), + }); }; const readOnly = (perm: CollectionPermission) => diff --git a/apps/web/src/app/organizations/manage/collection-add-edit.component.ts b/apps/web/src/app/organizations/manage/collection-add-edit.component.ts index bf381031404..6f0ad082920 100644 --- a/apps/web/src/app/organizations/manage/collection-add-edit.component.ts +++ b/apps/web/src/app/organizations/manage/collection-add-edit.component.ts @@ -11,7 +11,9 @@ import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { GroupResponse } from "@bitwarden/common/models/response/group.response"; + +import { GroupServiceAbstraction } from "../services/abstractions/group"; +import { GroupView } from "../views/group.view"; @Component({ selector: "app-collection-add-edit", @@ -31,7 +33,7 @@ export class CollectionAddEditComponent implements OnInit { title: string; name: string; externalId: string; - groups: GroupResponse[] = []; + groups: GroupView[] = []; formPromise: Promise; deletePromise: Promise; @@ -39,6 +41,7 @@ export class CollectionAddEditComponent implements OnInit { constructor( private apiService: ApiService, + private groupApiService: GroupServiceAbstraction, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private cryptoService: CryptoService, @@ -51,10 +54,8 @@ export class CollectionAddEditComponent implements OnInit { this.accessGroups = organization.useGroups; this.editMode = this.loading = this.collectionId != null; if (this.accessGroups) { - const groupsResponse = await this.apiService.getGroups(this.organizationId); - this.groups = groupsResponse.data - .map((r) => r) - .sort(Utils.getSortFunction(this.i18nService, "name")); + const groupsResponse = await this.groupApiService.getAll(this.organizationId); + this.groups = groupsResponse.sort(Utils.getSortFunction(this.i18nService, "name")); } this.orgKey = await this.cryptoService.getOrgKey(this.organizationId); @@ -97,7 +98,7 @@ export class CollectionAddEditComponent implements OnInit { this.loading = false; } - check(g: GroupResponse, select?: boolean) { + check(g: GroupView, select?: boolean) { if (g.accessAll) { return; } diff --git a/apps/web/src/app/organizations/manage/group-add-edit.component.html b/apps/web/src/app/organizations/manage/group-add-edit.component.html index d1055d86e85..f36aed52fbc 100644 --- a/apps/web/src/app/organizations/manage/group-add-edit.component.html +++ b/apps/web/src/app/organizations/manage/group-add-edit.component.html @@ -1,24 +1,13 @@ -