mirror of
https://github.com/bitwarden/browser
synced 2025-12-30 15:13:32 +00:00
[EC-73] edit collection modal (#3638)
* [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-73] feat: add inital version of modal using dialog service
* [EC-73] feat: create story for dialog
* [EC-73] feat: setup story with support for injected data
* [EC-73] feat: add inital version of subtitle
* [EC-73] feat: add tabs
* [EC-73] feat: initial version of collection info form
* [EC-73] feat: start of working form
* [EC-73] feat: add custom form validator
* [EC-73] fix: dialog directive names after rebase
* [EC-73] feat: use custom validator
* [EC-73] fix: story
* [EC-73] feat: allow parent picking
* [EC-73] feat: remove tabs to allow for merging
* [EC-73] feat: extend story with new and edit dialogs
* [EC-73] feat: change title depending on if editing or not
* [EC-73] fix: parent not connected to form
* [EC-73] feat: add organizationId to dialog data
* [EC-73] feat: only allow nesting within collections with access
* [EC-73] feat: handle loading with spinner
* [EC-73] feat: update collections on submit
* [EC-73] feat: reload on save
* [EC-73] feat: update story to work with latest changes
* [EC-73] feat: always fetch collections from server
* [EC-73] fix: do not submit if form invalid
* [EC-73] feat: create new collections using new ui
* [EC-73] fix: external id not being saved
* [EC-73] chore: move calls to separete collection admin service
* [EC-73] feat: use new admin views
* [EC-73] feat: implement deletion
* [EC-73] feat: add support for collection details in service
* [EC-73] fix: story
* [EC-73] fix: cancel button
* [EC-73] feat: re-add tabs
* [EC-73] fix: jslib service collection deps
* [EC-73] chore: rename component to collection-dialog
* [EC-73] chore: clean up collection api service which was replaced
* [EC-73] chore: restore collection.service
* [EC-73] chore: restore dialog component changes
* [EC-73] fix: move subscription to ngOnInit
* [EC-73] feat: disable padding when using tabbed content
* [EC-73] fix: new lint rules after merge
* Add Access Selector Component and Stories
* Cherry pick FormSelectionList
* Fix some problems caused from cherry-pick
* Fix some Web module problems caused from cherry-pick
* Move AccessSelector out of the root components directory.
Move UserType pipe to AccessSelectorModule
* Fix broken member access selector story
* Add organization feature module
* Undo changes to messages.json
* Fix messages.json
* Remove redundant CommonModule
* [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-599] Fix avatar/icon sizing
* [EC-599] Remove padding in permission column
* [EC-599] Make FormSelectionList operations immutable
* [EC-599] Integrate the multi-select component
* [EC-599] Handle readonly/access all edge cases
* [EC-599] Add initial unit tests
Also cleans up public interface for the AccessSelectorComponent. Fixes a bug found during unit test creation.
* [EC-599] Include item name in control labels
* [EC-599] Cleanup member email display
* [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-599] Review suggestions
- Change PermissionMode to Enum
- Rename permControl to permissionControl to be more clear
- Rename FormSelectionList file to kebab case.
- Move permission row boolean logic to named function for readability
* [EC-599] Cleanup AccessSelectorComponent tests
- Clarify test states
- Add tests for column rendering
- Add tests for permission mode
- Add id to column headers for testing
- Fix small permissionControl bug found during testing
* [EC-599] Add FormSelectionList unit tests
* [EC-73] chore: re-add collections page
* [EC-86] Rename new files using kebab-case
* [EC-73] chore: move component to shared org module
* Fix MultiSelect component styles and CSP error (#3841)
* Update Web styles and CSP to support MultiSelect component
- Include the MultiSelect module in the CL barrel file of exports
- Import the MultiSelect scss into the Web styles.scss
- Add the necessary sha256 hash to webpack CSP policy to support ngSelect inline styles
* Undo removal of 127.0.0.1 from webpack CSP
(cherry picked from commit 3ed1221f7f)
* [EC-73] feat: add empty access selector
* [EC-73] feat: add groups to access selector
* [EC-73] chore: improve storybook support
* [EC-73] feat: tweak item assignment
* [EC-73] feat: add support for showing users
* [EC-73] feat: use async actions
* [EC-73] chore: clean up casting
* [EC-73] fix: permissions not loading correctly in access selector
* [EC-73] feat: implement saving group permissions
* [EC-73] feat: rename to collection access selection view
* [EC-73] feat: save users as well
* [EC-73] fix: access selector usage
* [EC-73] feat: new collection creation
* [EC-73] feat: fetch users from collection details
* [EC-73] chore: clean up
* [EC-73] fix: circular dependency issues
* [EC-73] fix: import shared module directly to workaround build issues
* [EC-73] fix: missing dependencies in story
* [EC-73] chore: move story
* [EC-73] fix: manual cherry pick permission bug fix
* [EC-73] feat: hide delete button if no permission
* [EC-73] feat: properly handle orgs without groups
* [EC-73] fix: use correct functions in template
* [EC-73] feat: properly handle non-existing parent
* [EC-73] chore: use double ngIf instead of else template
* [EC-73] fix: add type to dialog ref
* [EC-73] fix: restrict field modifiers
* [EC-73] fix: use result enum directly
* [EC-73] fix: simplify mapping logic
* [EC-73]
* [EC-73] feat: add story for free orgs without groups
* [EC-73] fix: parametrized i18n
* [EC-73] feat: create new shared org module
* [EC-73] feat: move collection dialog to shared
* [EC-73] feat: move access selector to shared
* [EC-73] feat: create core organization module
* [EC-73] feat: move collection admin service to web
* [EC-73] feat: move collection admin views to web
* [EC-73] fix: missing i18n
* [EC-73] fix: refactor for type safety
* [EC-73] fix: storybook not compiling again
* [EC-73] feat: use helper function to open dialog
* [EC-73] chore: remove comment
* [EC-73] fix: revert permission fix
* [EC-73] fix: only show delete if in edit mode
* [EC-73] chore: remove ngIf else in template
* [EC-73] fix: add missing appA11yTitle
* [EC-73] chore: rename remove to delete
* [EC-73] chore: refactor ngOnInit
* [EC-73] fix: dialog position strategy
* [EC-73] fix: revert spinner to old way of doing it
Signed-off-by: Jacob Fink <jfink@bitwarden.com>
Co-authored-by: Shane Melton <smelton@bitwarden.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: Thomas Rittson <eliykat@users.noreply.github.com>
This commit is contained in:
@@ -92,7 +92,7 @@ import { BillingPaymentResponse } from "../models/response/billing-payment.respo
|
||||
import { BreachAccountResponse } from "../models/response/breach-account.response";
|
||||
import { CipherResponse } from "../models/response/cipher.response";
|
||||
import {
|
||||
CollectionGroupDetailsResponse,
|
||||
CollectionAccessDetailsResponse,
|
||||
CollectionResponse,
|
||||
} from "../models/response/collection.response";
|
||||
import { DeviceVerificationResponse } from "../models/response/device-verification.response";
|
||||
@@ -314,7 +314,7 @@ export abstract class ApiService {
|
||||
getCollectionDetails: (
|
||||
organizationId: string,
|
||||
id: string
|
||||
) => Promise<CollectionGroupDetailsResponse>;
|
||||
) => Promise<CollectionAccessDetailsResponse>;
|
||||
getUserCollections: () => Promise<ListResponse<CollectionResponse>>;
|
||||
getCollections: (organizationId: string) => Promise<ListResponse<CollectionResponse>>;
|
||||
getCollectionUsers: (organizationId: string, id: string) => Promise<SelectionReadOnlyResponse[]>;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { GroupView } from "../../models/view/group-view";
|
||||
|
||||
export class GroupServiceAbstraction {
|
||||
delete: (orgId: string, groupId: string) => Promise<void>;
|
||||
deleteMany: (orgId: string, groupIds: string[]) => Promise<GroupView[]>;
|
||||
|
||||
get: (orgId: string, groupId: string) => Promise<GroupView>;
|
||||
getAll: (orgId: string) => Promise<GroupView[]>;
|
||||
}
|
||||
2
libs/common/src/abstractions/group/index.ts
Normal file
2
libs/common/src/abstractions/group/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./group.service.abstraction";
|
||||
export * from "./responses/group-response";
|
||||
@@ -0,0 +1,31 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { SelectionReadOnlyResponse } from "../../../models/response/selection-read-only.response";
|
||||
|
||||
export class GroupResponse extends BaseResponse {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
name: string;
|
||||
accessAll: boolean;
|
||||
externalId: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.organizationId = this.getResponseProperty("OrganizationId");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.accessAll = this.getResponseProperty("AccessAll");
|
||||
this.externalId = this.getResponseProperty("ExternalId");
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupDetailsResponse extends GroupResponse {
|
||||
collections: SelectionReadOnlyResponse[] = [];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
const collections = this.getResponseProperty("Collections");
|
||||
if (collections != null) {
|
||||
this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,6 +440,10 @@ export class Utils {
|
||||
return mobile || win.navigator.userAgent.match(/iPad/i) != null;
|
||||
}
|
||||
|
||||
static delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
private static isAppleMobile(win: Window) {
|
||||
return (
|
||||
win.navigator.userAgent.match(/iPhone/i) != null ||
|
||||
|
||||
@@ -6,6 +6,7 @@ export class CollectionRequest {
|
||||
name: string;
|
||||
externalId: string;
|
||||
groups: SelectionReadOnlyRequest[] = [];
|
||||
users: SelectionReadOnlyRequest[] = [];
|
||||
|
||||
constructor(collection?: Collection) {
|
||||
if (collection == null) {
|
||||
|
||||
@@ -25,8 +25,9 @@ export class CollectionDetailsResponse extends CollectionResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionGroupDetailsResponse extends CollectionResponse {
|
||||
export class CollectionAccessDetailsResponse extends CollectionResponse {
|
||||
groups: SelectionReadOnlyResponse[] = [];
|
||||
users: SelectionReadOnlyResponse[] = [];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -34,5 +35,10 @@ export class CollectionGroupDetailsResponse extends CollectionResponse {
|
||||
if (groups != null) {
|
||||
this.groups = groups.map((g: any) => new SelectionReadOnlyResponse(g));
|
||||
}
|
||||
|
||||
const users = this.getResponseProperty("Users");
|
||||
if (users != null) {
|
||||
this.users = users.map((g: any) => new SelectionReadOnlyResponse(g));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Collection } from "../domain/collection";
|
||||
import { ITreeNodeObject } from "../domain/tree-node";
|
||||
import { CollectionGroupDetailsResponse } from "../response/collection.response";
|
||||
import { CollectionResponse } from "../response/collection.response";
|
||||
|
||||
import { View } from "./view";
|
||||
|
||||
@@ -12,7 +12,7 @@ export class CollectionView implements View, ITreeNodeObject {
|
||||
readOnly: boolean = null;
|
||||
hidePasswords: boolean = null;
|
||||
|
||||
constructor(c?: Collection | CollectionGroupDetailsResponse) {
|
||||
constructor(c?: Collection | CollectionResponse) {
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
|
||||
17
libs/common/src/models/view/group-view.ts
Normal file
17
libs/common/src/models/view/group-view.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { GroupResponse } from "../../abstractions/group";
|
||||
import { SelectionReadOnlyResponse } from "../response/selection-read-only.response";
|
||||
|
||||
import { View } from "./view";
|
||||
|
||||
export class GroupView implements View {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
name: string;
|
||||
accessAll: boolean;
|
||||
externalId: string;
|
||||
collections: SelectionReadOnlyResponse[] = [];
|
||||
|
||||
static fromResponse(response: GroupResponse) {
|
||||
return Object.assign(new GroupView(), response);
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ import { BillingPaymentResponse } from "../models/response/billing-payment.respo
|
||||
import { BreachAccountResponse } from "../models/response/breach-account.response";
|
||||
import { CipherResponse } from "../models/response/cipher.response";
|
||||
import {
|
||||
CollectionGroupDetailsResponse,
|
||||
CollectionAccessDetailsResponse,
|
||||
CollectionResponse,
|
||||
} from "../models/response/collection.response";
|
||||
import { DeviceVerificationResponse } from "../models/response/device-verification.response";
|
||||
@@ -810,7 +810,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
async getCollectionDetails(
|
||||
organizationId: string,
|
||||
id: string
|
||||
): Promise<CollectionGroupDetailsResponse> {
|
||||
): Promise<CollectionAccessDetailsResponse> {
|
||||
const r = await this.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/collections/" + id + "/details",
|
||||
@@ -818,7 +818,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new CollectionGroupDetailsResponse(r);
|
||||
return new CollectionAccessDetailsResponse(r);
|
||||
}
|
||||
|
||||
async getUserCollections(): Promise<ListResponse<CollectionResponse>> {
|
||||
|
||||
65
libs/common/src/services/group/group.service.ts
Normal file
65
libs/common/src/services/group/group.service.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import {
|
||||
GroupDetailsResponse,
|
||||
GroupResponse,
|
||||
GroupServiceAbstraction,
|
||||
} from "../../abstractions/group";
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
import { GroupView } from "../../models/view/group-view";
|
||||
|
||||
import { OrganizationGroupBulkRequest } from "./requests/organization-group-bulk-request";
|
||||
|
||||
export class GroupService implements GroupServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
async delete(orgId: string, groupId: string): Promise<void> {
|
||||
await this.apiService.send(
|
||||
"DELETE",
|
||||
"/organizations/" + orgId + "/groups/" + groupId,
|
||||
null,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
async deleteMany(orgId: string, groupIds: string[]): Promise<GroupView[]> {
|
||||
const request = new OrganizationGroupBulkRequest(groupIds);
|
||||
|
||||
const r = await this.apiService.send(
|
||||
"DELETE",
|
||||
"/organizations/" + orgId + "/groups",
|
||||
request,
|
||||
true,
|
||||
true
|
||||
);
|
||||
const listResponse = new ListResponse(r, GroupResponse);
|
||||
|
||||
return listResponse.data?.map((gr) => GroupView.fromResponse(gr)) ?? [];
|
||||
}
|
||||
|
||||
async get(orgId: string, groupId: string): Promise<GroupView> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + orgId + "/groups/" + groupId + "/details",
|
||||
null,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
return GroupView.fromResponse(new GroupDetailsResponse(r));
|
||||
}
|
||||
|
||||
async getAll(orgId: string): Promise<GroupView[]> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + orgId + "/groups",
|
||||
null,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
const listResponse = new ListResponse(r, GroupDetailsResponse);
|
||||
|
||||
return listResponse.data?.map((gr) => GroupView.fromResponse(gr)) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class OrganizationGroupBulkRequest {
|
||||
ids: string[];
|
||||
|
||||
constructor(ids: string[]) {
|
||||
this.ids = ids == null ? [] : ids;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user