@memberjunction/ng-entity-permissions 5.40.2 → 5.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=entity-permissions-grid.component.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-permissions-grid.component.test.d.ts","sourceRoot":"","sources":["../../../../src/lib/grid/__tests__/entity-permissions-grid.component.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ /**
3
+ * Recompute-trigger coverage for EntityPermissionsGridComponent's roleNameMap
4
+ * precompute (PR #2841): the role-name lookup is built ONCE on data load
5
+ * (`buildRoleNameMap`), keyed by NormalizeUUID(roleID), so the per-row template
6
+ * binding `getRoleName` is an O(1) Map read instead of an O(roles) UUIDsEqual
7
+ * scan over `md.Roles` every change-detection pass.
8
+ *
9
+ * These tests pin:
10
+ * (1) getRoleName resolves a known role after the map is built,
11
+ * (2) case variance (SQL upper vs Postgres lower) resolves via NormalizeUUID,
12
+ * (3) empty / missing / unknown roleID returns '' (no throw),
13
+ * (4) the map is REBUILT on each load — a role removed from the provider's
14
+ * list no longer resolves.
15
+ *
16
+ * Uses the real NormalizeUUID (`@memberjunction/global`); the component is `new`'d
17
+ * with a mocked provider exposing `Roles`.
18
+ */
19
+ vi.mock('@angular/core', () => ({
20
+ Component: () => (t) => t,
21
+ Input: () => () => { },
22
+ Output: () => () => { },
23
+ EventEmitter: class {
24
+ emit() { }
25
+ },
26
+ ChangeDetectorRef: class {
27
+ detectChanges() { }
28
+ markForCheck() { }
29
+ },
30
+ OnInit: class {
31
+ },
32
+ OnChanges: class {
33
+ },
34
+ }));
35
+ vi.mock('@memberjunction/ng-base-types', () => {
36
+ class MockBaseAngularComponent {
37
+ Provider = null;
38
+ get ProviderToUse() {
39
+ return this.Provider;
40
+ }
41
+ }
42
+ return { BaseAngularComponent: MockBaseAngularComponent };
43
+ });
44
+ vi.mock('@memberjunction/core', () => ({
45
+ Metadata: class {
46
+ },
47
+ RunView: class {
48
+ },
49
+ }));
50
+ vi.mock('@memberjunction/core-entities', () => ({
51
+ MJEntityPermissionEntity: class {
52
+ },
53
+ }));
54
+ import { EntityPermissionsGridComponent } from '../entity-permissions-grid.component';
55
+ function makeComponent(roles) {
56
+ const component = new EntityPermissionsGridComponent();
57
+ component.Provider = { Roles: roles };
58
+ return component;
59
+ }
60
+ /** Call the private builder (the real component invokes it inside Refresh()). */
61
+ function build(component) {
62
+ component.buildRoleNameMap();
63
+ }
64
+ describe('EntityPermissionsGridComponent — roleNameMap precompute', () => {
65
+ let component;
66
+ const roles = [
67
+ { ID: 'AAAAAAAA-1111-1111-1111-111111111111', Name: 'Administrator' },
68
+ { ID: 'bbbbbbbb-2222-2222-2222-222222222222', Name: 'Integration' },
69
+ ];
70
+ beforeEach(() => {
71
+ component = makeComponent(roles);
72
+ build(component);
73
+ });
74
+ it('resolves a role name once the map is built', () => {
75
+ expect(component.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('Administrator');
76
+ expect(component.getRoleName('bbbbbbbb-2222-2222-2222-222222222222')).toBe('Integration');
77
+ });
78
+ it('resolves across UUID case variance (lookup upper vs stored lower, and vice versa)', () => {
79
+ // stored uppercase, looked up lowercase
80
+ expect(component.getRoleName('aaaaaaaa-1111-1111-1111-111111111111')).toBe('Administrator');
81
+ // stored lowercase, looked up uppercase
82
+ expect(component.getRoleName('BBBBBBBB-2222-2222-2222-222222222222')).toBe('Integration');
83
+ });
84
+ it('returns empty string for an empty roleID (no throw)', () => {
85
+ expect(component.getRoleName('')).toBe('');
86
+ });
87
+ it('returns empty string for an unknown roleID', () => {
88
+ expect(component.getRoleName('FFFFFFFF-9999-9999-9999-999999999999')).toBe('');
89
+ });
90
+ it('rebuilds the map on each load — a removed role no longer resolves', () => {
91
+ expect(component.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('Administrator');
92
+ // provider now exposes a DIFFERENT, smaller role set; rebuild
93
+ component.Provider = {
94
+ Roles: [{ ID: 'bbbbbbbb-2222-2222-2222-222222222222', Name: 'Integration' }],
95
+ };
96
+ build(component);
97
+ expect(component.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('');
98
+ expect(component.getRoleName('bbbbbbbb-2222-2222-2222-222222222222')).toBe('Integration');
99
+ });
100
+ it('handles an empty provider role list — every lookup returns ""', () => {
101
+ const empty = makeComponent([]);
102
+ build(empty);
103
+ expect(empty.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('');
104
+ });
105
+ });
106
+ //# sourceMappingURL=entity-permissions-grid.component.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-permissions-grid.component.test.js","sourceRoot":"","sources":["../../../../src/lib/grid/__tests__/entity-permissions-grid.component.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest';\n\n/**\n * Recompute-trigger coverage for EntityPermissionsGridComponent's roleNameMap\n * precompute (PR #2841): the role-name lookup is built ONCE on data load\n * (`buildRoleNameMap`), keyed by NormalizeUUID(roleID), so the per-row template\n * binding `getRoleName` is an O(1) Map read instead of an O(roles) UUIDsEqual\n * scan over `md.Roles` every change-detection pass.\n *\n * These tests pin:\n * (1) getRoleName resolves a known role after the map is built,\n * (2) case variance (SQL upper vs Postgres lower) resolves via NormalizeUUID,\n * (3) empty / missing / unknown roleID returns '' (no throw),\n * (4) the map is REBUILT on each load — a role removed from the provider's\n * list no longer resolves.\n *\n * Uses the real NormalizeUUID (`@memberjunction/global`); the component is `new`'d\n * with a mocked provider exposing `Roles`.\n */\n\nvi.mock('@angular/core', () => ({\n Component: () => (t: Function) => t,\n Input: () => () => {},\n Output: () => () => {},\n EventEmitter: class { emit() {} },\n ChangeDetectorRef: class { detectChanges() {} markForCheck() {} },\n OnInit: class {},\n OnChanges: class {},\n}));\n\nvi.mock('@memberjunction/ng-base-types', () => {\n class MockBaseAngularComponent {\n Provider: unknown = null;\n get ProviderToUse(): unknown {\n return this.Provider;\n }\n }\n return { BaseAngularComponent: MockBaseAngularComponent };\n});\n\nvi.mock('@memberjunction/core', () => ({\n Metadata: class {},\n RunView: class {},\n}));\n\nvi.mock('@memberjunction/core-entities', () => ({\n MJEntityPermissionEntity: class {},\n}));\n\nimport { EntityPermissionsGridComponent } from '../entity-permissions-grid.component';\n\ninterface MockRole {\n ID: string;\n Name: string;\n}\n\nfunction makeComponent(roles: MockRole[]): EntityPermissionsGridComponent {\n const component = new EntityPermissionsGridComponent();\n (component as unknown as { Provider: { Roles: MockRole[] } }).Provider = { Roles: roles };\n return component;\n}\n\n/** Call the private builder (the real component invokes it inside Refresh()). */\nfunction build(component: EntityPermissionsGridComponent): void {\n (component as unknown as { buildRoleNameMap: () => void }).buildRoleNameMap();\n}\n\ndescribe('EntityPermissionsGridComponent — roleNameMap precompute', () => {\n let component: EntityPermissionsGridComponent;\n const roles: MockRole[] = [\n { ID: 'AAAAAAAA-1111-1111-1111-111111111111', Name: 'Administrator' },\n { ID: 'bbbbbbbb-2222-2222-2222-222222222222', Name: 'Integration' },\n ];\n\n beforeEach(() => {\n component = makeComponent(roles);\n build(component);\n });\n\n it('resolves a role name once the map is built', () => {\n expect(component.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('Administrator');\n expect(component.getRoleName('bbbbbbbb-2222-2222-2222-222222222222')).toBe('Integration');\n });\n\n it('resolves across UUID case variance (lookup upper vs stored lower, and vice versa)', () => {\n // stored uppercase, looked up lowercase\n expect(component.getRoleName('aaaaaaaa-1111-1111-1111-111111111111')).toBe('Administrator');\n // stored lowercase, looked up uppercase\n expect(component.getRoleName('BBBBBBBB-2222-2222-2222-222222222222')).toBe('Integration');\n });\n\n it('returns empty string for an empty roleID (no throw)', () => {\n expect(component.getRoleName('')).toBe('');\n });\n\n it('returns empty string for an unknown roleID', () => {\n expect(component.getRoleName('FFFFFFFF-9999-9999-9999-999999999999')).toBe('');\n });\n\n it('rebuilds the map on each load — a removed role no longer resolves', () => {\n expect(component.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('Administrator');\n\n // provider now exposes a DIFFERENT, smaller role set; rebuild\n (component as unknown as { Provider: { Roles: MockRole[] } }).Provider = {\n Roles: [{ ID: 'bbbbbbbb-2222-2222-2222-222222222222', Name: 'Integration' }],\n };\n build(component);\n\n expect(component.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('');\n expect(component.getRoleName('bbbbbbbb-2222-2222-2222-222222222222')).toBe('Integration');\n });\n\n it('handles an empty provider role list — every lookup returns \"\"', () => {\n const empty = makeComponent([]);\n build(empty);\n expect(empty.getRoleName('AAAAAAAA-1111-1111-1111-111111111111')).toBe('');\n });\n});\n"]}
@@ -18,11 +18,23 @@ export declare class EntityPermissionsGridComponent extends BaseAngularComponent
18
18
  permissions: MJEntityPermissionEntity[];
19
19
  gridHeight: number;
20
20
  isLoading: boolean;
21
+ /**
22
+ * Role-name lookup keyed by NormalizeUUID(roleID). Built once when roles are
23
+ * available (data load) so {@link getRoleName} is an O(1) Map read per table
24
+ * row instead of an O(roles) UUIDsEqual scan over `md.Roles` every CD cycle.
25
+ */
26
+ private roleNameMap;
21
27
  constructor();
22
28
  ngOnInit(): void;
23
29
  ngOnChanges(changes: SimpleChanges): void;
24
30
  ValidateInputs(): void;
25
31
  Refresh(): Promise<void>;
32
+ /**
33
+ * Populates {@link roleNameMap} from the provider's role list. Called on data
34
+ * load so the per-row template binding {@link getRoleName} never scans
35
+ * `md.Roles` during change detection.
36
+ */
37
+ private buildRoleNameMap;
26
38
  getRoleName(roleID: string): string;
27
39
  savePermissions(): Promise<void>;
28
40
  cancelEdit(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"entity-permissions-grid.component.d.ts","sourceRoot":"","sources":["../../../src/lib/grid/entity-permissions-grid.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,EAAS,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGzG,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAIzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AACrE,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,qBAAqB,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAC9D,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,qBAMa,8BAA+B,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS;IAC1F,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAY;IACnC,UAAU,EAAG,MAAM,CAAC;IACpB,QAAQ,EAAG,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAK;IAExB,iBAAiB,6CAAoD;IAExE,WAAW,EAAE,wBAAwB,EAAE,CAAM;IAC7C,UAAU,EAAE,MAAM,CAAO;IACzB,SAAS,EAAE,OAAO,CAAS;;IAMlC,QAAQ,IAAI,IAAI;IAIhB,WAAW,CAAC,OAAO,EAAE,aAAa;IAOlC,cAAc;IAMR,OAAO;IA6EN,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAM7B,eAAe;IAkBf,UAAU;IAWvB,IAAW,mBAAmB,IAAI,MAAM,CAEvC;IAED,SAAS,CAAC,uBAAuB,CAAC,CAAC,EAAE,wBAAwB,GAAG,OAAO;IAShE,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ;IAoDhE,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,wBAAwB;IAOjE,OAAO,CAAC,UAAU,EAAE,wBAAwB;IA0B5C,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,UAAU,EAAE,wBAAwB,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAAE,cAAc,EAAE,OAAO;yCAnPtJ,8BAA8B;2CAA9B,8BAA8B;CAiR1C"}
1
+ {"version":3,"file":"entity-permissions-grid.component.d.ts","sourceRoot":"","sources":["../../../src/lib/grid/entity-permissions-grid.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,EAAS,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGzG,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAIzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AACrE,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,qBAAqB,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAC9D,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,qBAMa,8BAA+B,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS;IAC1F,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAY;IACnC,UAAU,EAAG,MAAM,CAAC;IACpB,QAAQ,EAAG,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAK;IAExB,iBAAiB,6CAAoD;IAExE,WAAW,EAAE,wBAAwB,EAAE,CAAM;IAC7C,UAAU,EAAE,MAAM,CAAO;IACzB,SAAS,EAAE,OAAO,CAAS;IAElC;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAA6B;;IAMhD,QAAQ,IAAI,IAAI;IAIhB,WAAW,CAAC,OAAO,EAAE,aAAa;IAOlC,cAAc;IAMR,OAAO;IA8Eb;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAQjB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAK7B,eAAe;IAkBf,UAAU;IAWvB,IAAW,mBAAmB,IAAI,MAAM,CAEvC;IAED,SAAS,CAAC,uBAAuB,CAAC,CAAC,EAAE,wBAAwB,GAAG,OAAO;IAShE,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ;IAoDhE,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,wBAAwB;IAOjE,OAAO,CAAC,UAAU,EAAE,wBAAwB;IA0B5C,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,UAAU,EAAE,wBAAwB,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAAE,cAAc,EAAE,OAAO;yCAvQtJ,8BAA8B;2CAA9B,8BAA8B;CAqS1C"}
@@ -1,6 +1,6 @@
1
1
  import { Component, Output, EventEmitter, Input } from '@angular/core';
2
2
  import { RunView } from '@memberjunction/core';
3
- import { UUIDsEqual } from '@memberjunction/global';
3
+ import { UUIDsEqual, NormalizeUUID } from '@memberjunction/global';
4
4
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
5
5
  import * as i0 from "@angular/core";
6
6
  import * as i1 from "@angular/common";
@@ -140,6 +140,12 @@ export class EntityPermissionsGridComponent extends BaseAngularComponent {
140
140
  permissions = [];
141
141
  gridHeight = 750;
142
142
  isLoading = false;
143
+ /**
144
+ * Role-name lookup keyed by NormalizeUUID(roleID). Built once when roles are
145
+ * available (data load) so {@link getRoleName} is an O(1) Map read per table
146
+ * row instead of an O(roles) UUIDsEqual scan over `md.Roles` every CD cycle.
147
+ */
148
+ roleNameMap = new Map();
143
149
  constructor() {
144
150
  super();
145
151
  }
@@ -163,6 +169,7 @@ export class EntityPermissionsGridComponent extends BaseAngularComponent {
163
169
  const startTime = new Date().getTime();
164
170
  this.isLoading = true;
165
171
  const md = this.ProviderToUse;
172
+ this.buildRoleNameMap();
166
173
  const entity = md.Entities.find(e => e.Name === this.EntityName);
167
174
  if (this.Mode === 'Entity' && !entity)
168
175
  throw new Error("Entity not found: " + this.EntityName);
@@ -227,10 +234,22 @@ export class EntityPermissionsGridComponent extends BaseAngularComponent {
227
234
  }
228
235
  this.isLoading = false;
229
236
  }
230
- getRoleName(roleID) {
237
+ /**
238
+ * Populates {@link roleNameMap} from the provider's role list. Called on data
239
+ * load so the per-row template binding {@link getRoleName} never scans
240
+ * `md.Roles` during change detection.
241
+ */
242
+ buildRoleNameMap() {
231
243
  const md = this.ProviderToUse;
232
- const r = md.Roles.find(r => UUIDsEqual(r.ID, roleID));
233
- return r ? r.Name : '';
244
+ this.roleNameMap.clear();
245
+ for (const r of md.Roles) {
246
+ this.roleNameMap.set(NormalizeUUID(r.ID), r.Name);
247
+ }
248
+ }
249
+ getRoleName(roleID) {
250
+ if (!roleID)
251
+ return '';
252
+ return this.roleNameMap.get(NormalizeUUID(roleID)) ?? '';
234
253
  }
235
254
  async savePermissions() {
236
255
  if (this.NumDirtyPermissions > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"entity-permissions-grid.component.js","sourceRoot":"","sources":["../../../src/lib/grid/entity-permissions-grid.component.ts","../../../src/lib/grid/entity-permissions-grid.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Output, EventEmitter, OnInit, Input, SimpleChanges, OnChanges } from '@angular/core';\n\nimport { Metadata, RunView } from '@memberjunction/core';\nimport { MJEntityPermissionEntity } from '@memberjunction/core-entities';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nexport type EntityPermissionChangedEvent = {\n EntityName: string,\n RoleID: string\n PermissionTypeChanged: 'Read' | 'Create' | 'Update' | 'Delete'\n Value: boolean\n Cancel: boolean\n}\n \n@Component({\n standalone: false,\n selector: 'mj-entity-permissions-grid',\n templateUrl: './entity-permissions-grid.component.html',\n styleUrls: ['./entity-permissions-grid.component.css']\n})\nexport class EntityPermissionsGridComponent extends BaseAngularComponent implements OnInit, OnChanges {\n @Input() Mode: 'Entity' | 'Role' = 'Entity';\n @Input() EntityName!: string; // used when Mode is 'Entity'\n @Input() RoleName!: string; // used when Mode is 'Role'\n @Input() BottomMargin: number = 0;\n\n @Output() PermissionChanged = new EventEmitter<EntityPermissionChangedEvent>();\n\n public permissions: MJEntityPermissionEntity[] = [];\n public gridHeight: number = 750;\n public isLoading: boolean = false;\n\n constructor() {\n super();\n }\n\n ngOnInit(): void {\n this.Refresh()\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes.EntityName && !changes.EntityName.isFirstChange()) {\n // If EntityName has changed and it's not the first change (initialization)\n this.Refresh();\n }\n }\n\n ValidateInputs() {\n if (this.Mode === 'Entity' && (!this.EntityName || this.EntityName.length === 0))\n throw new Error(\"EntityName is required when Mode is 'Entity'\")\n if (this.Mode === 'Role' && (!this.RoleName || this.RoleName.length === 0))\n throw new Error(\"RoleName is required when Mode is 'Role'\") \n }\n async Refresh() { \n this.ValidateInputs();\n const startTime = new Date().getTime();\n this.isLoading = true\n\n const md = this.ProviderToUse;\n const entity = md.Entities.find(e => e.Name === this.EntityName);\n if (this.Mode === 'Entity' && !entity)\n throw new Error(\"Entity not found: \" + this.EntityName)\n\n const r = md.Roles.find(r => r.Name === this.RoleName);\n if (this.Mode === 'Role' && !r)\n throw new Error(\"Role not found: \" + this.RoleName)\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const filter: string = this.Mode === 'Entity' ? `EntityID='${entity!.ID}'` : `RoleName='${r?.Name}'`;\n const result = await rv.RunView({\n EntityName: 'MJ: Entity Permissions',\n ExtraFilter: filter,\n OrderBy: 'RoleName ASC, Entity ASC',\n ResultType: 'entity_object'\n })\n if (result.Success) {\n // we have all of the saved permissions now\n // the post-process we need to do now is to see if there are any roles that don't have any existing permissions and if so, we need to create \n // new permission records for them. We won't actually consider those \"Dirty\" and save those unless the user actually selects one or more\n // to turn on, we are just doing this to make the grid easy to manage from the user perspective.\n const existingPermissions = <MJEntityPermissionEntity[]>result.Results;\n const roles = md.Roles;\n\n if (this.Mode === 'Entity') {\n const rolesWithNoPermissions = roles.filter(r => !existingPermissions.some(p => UUIDsEqual(p.RoleID, r.ID)));\n for (const r of rolesWithNoPermissions) {\n const p = await md.GetEntityObject<MJEntityPermissionEntity>('MJ: Entity Permissions')\n \n await p.LoadFromData({\n ID: null,\n Entity: entity!.Name,\n EntityID: entity!.ID,\n RoleID: r!.ID,\n CanRead: false,\n CanCreate: false,\n CanUpdate: false,\n CanDelete: false\n });\n existingPermissions.push(p);\n }\n this.permissions = existingPermissions;\n }\n else if (this.Mode === 'Role') {\n // for the mode of Role, that means we want to show all entities and their permissions for the given role\n const entitiesWithNoPermissions = md.Entities.filter(e => !existingPermissions.some(p => UUIDsEqual(p.EntityID, e.ID)));\n for (const e of entitiesWithNoPermissions) {\n const p = await md.GetEntityObject<MJEntityPermissionEntity>('MJ: Entity Permissions')\n \n await p.LoadFromData({\n ID: null,\n Entity: e.Name,\n EntityID: e.ID,\n RoleName: r!.Name,\n CanRead: false,\n CanCreate: false,\n CanUpdate: false,\n CanDelete: false\n });\n existingPermissions.push(p);\n }\n this.permissions = existingPermissions.sort((a, b) => a.Entity!.localeCompare(b.Entity!)); \n }\n }\n else {\n throw new Error(\"Error loading entity permissions: \" + result.ErrorMessage)\n }\n this.isLoading = false\n }\n \n\n public getRoleName(roleID: string): string {\n const md = this.ProviderToUse;\n const r = md.Roles.find(r => UUIDsEqual(r.ID, roleID));\n return r ? r.Name : '';\n }\n \n public async savePermissions() {\n if (this.NumDirtyPermissions > 0) {\n // iterate through each permisison and for the ones that are dirty, add to transaction group then commit at once\n const md = this.ProviderToUse;\n const tg = await md.CreateTransactionGroup();\n let itemCount: number = 0;\n for (const p of this.permissions) {\n if (this.IsPermissionReallyDirty(p)) {\n p.TransactionGroup = tg;\n itemCount++;\n await p.Save(); \n }\n }\n if (itemCount > 0)\n await tg.Submit();\n }\n }\n\n public async cancelEdit() {\n if (this.NumDirtyPermissions > 0) {\n // go through and revert each permission that is REALLY dirty\n this.permissions.forEach(p => {\n if (this.IsPermissionReallyDirty(p)) {\n p.Revert();\n }\n })\n }\n }\n\n public get NumDirtyPermissions(): number {\n return this.permissions.filter(p => this.IsPermissionReallyDirty(p)).length;\n }\n\n protected IsPermissionReallyDirty(p: MJEntityPermissionEntity): boolean {\n if (!p.Dirty)\n return false;\n else if (p.IsSaved)\n return true;\n else\n return p.CanRead || p.CanCreate || p.CanUpdate || p.CanDelete; // if we have a new record, only consider it dirty if at least one permission is true\n }\n\n public flipAllPermissions(type: 'Read' | 'Create' | 'Update' | 'Delete') {\n // first, figure out what we have the majority of, if we have more ON, then we will flip to OFF, otherwise we will flip to ON\n let onCount = 0;\n let offCount = 0;\n this.permissions.forEach(p => {\n if (type === 'Read') {\n if (p.CanRead)\n onCount++;\n else\n offCount++;\n }\n else if (type === 'Create') {\n if (p.CanCreate)\n onCount++;\n else\n offCount++;\n }\n else if (type === 'Update') {\n if (p.CanUpdate)\n onCount++;\n else\n offCount++;\n }\n else if (type === 'Delete') {\n if (p.CanDelete)\n onCount++;\n else\n offCount++;\n }\n })\n const value = offCount > onCount;\n\n // now set the permission for each permission record\n for (const p of this.permissions) {\n switch (type) {\n case 'Read':\n p.CanRead = value;\n break;\n case 'Create':\n p.CanCreate = value;\n break;\n case 'Update':\n p.CanUpdate = value;\n break;\n case 'Delete':\n p.CanDelete = value;\n break;\n }\n this.flipPermission(undefined, p, type, false); // call this function but tell it to NOT actually flip the permission, just to fire the event\n }\n }\n\n public revertRow(event: MouseEvent, permission: MJEntityPermissionEntity) {\n if (this.IsPermissionReallyDirty(permission)) {\n permission.Revert();\n event.stopPropagation(); // don't bubble up to the parent row because that will do something else...\n }\n }\n\n public flipRow(permission: MJEntityPermissionEntity) {\n // if 2 or more are on, flip all to off, otherwise flip all to on\n const onCount = (permission.CanRead ? 1 : 0) + (permission.CanCreate ? 1 : 0) + (permission.CanUpdate ? 1 : 0) + (permission.CanDelete ? 1 : 0);\n const newValue = onCount < 2;\n\n if (permission.CanRead !== newValue) {\n permission.CanRead = newValue;\n this.flipPermission(undefined, permission, 'Read', false); // fire the event but don't actually flip the permission\n }\n\n if (permission.CanCreate !== newValue) {\n permission.CanCreate = newValue;\n this.flipPermission(undefined, permission, 'Create', false); // fire the event but don't actually flip the permission\n }\n\n if (permission.CanUpdate !== newValue) {\n permission.CanUpdate = newValue;\n this.flipPermission(undefined, permission, 'Update', false); // fire the event but don't actually flip the permission\n }\n\n if (permission.CanDelete !== newValue) {\n permission.CanDelete = newValue;\n this.flipPermission(undefined, permission, 'Delete', false); // fire the event but don't actually flip the permission\n }\n }\n\n public flipPermission(event: MouseEvent | undefined, permission: MJEntityPermissionEntity, type: 'Read' | 'Create' | 'Update' | 'Delete', flipPermission: boolean) {\n if (flipPermission) {\n switch (type) {\n case 'Read':\n permission.CanRead = !permission.CanRead;\n break;\n case 'Create':\n permission.CanCreate = !permission.CanCreate;\n break;\n case 'Update':\n permission.CanUpdate = !permission.CanUpdate;\n break;\n case 'Delete':\n permission.CanDelete = !permission.CanDelete;\n break;\n }\n }\n // always fire the event\n const value = type === 'Read' ? permission.CanRead : type === 'Create' ? permission.CanCreate : type === 'Update' ? permission.CanUpdate : permission.CanDelete;\n this.PermissionChanged.emit({\n EntityName: this.EntityName,\n RoleID: permission.RoleID!,\n PermissionTypeChanged: type,\n Value: value,\n Cancel: false\n })\n\n if (!flipPermission && event)\n event.stopPropagation();\n }\n}\n","<div>\n @if (isLoading) {\n <div><mj-loading [showText]=\"false\" size=\"medium\"></mj-loading></div>\n }\n <button [disabled]=\"NumDirtyPermissions === 0\" mjButton (click)=\"savePermissions()\">Save</button>\n <button [disabled]=\"NumDirtyPermissions === 0\" mjButton (click)=\"cancelEdit()\">Cancel</button>\n @if (!isLoading) {\n <table class=\"grid\">\n <thead>\n <tr>\n @if (this.Mode==='Entity') {\n <th>Role</th>\n }\n @if (this.Mode==='Role') {\n <th>Entity</th>\n }\n <th (click)=\"flipAllPermissions('Read')\">Read</th>\n <th (click)=\"flipAllPermissions('Create')\">Create</th>\n <th (click)=\"flipAllPermissions('Update')\">Update</th>\n <th (click)=\"flipAllPermissions('Delete')\">Delete</th>\n </tr>\n </thead>\n <tbody>\n @for (permission of permissions; track permission) {\n <tr [ngClass]=\"IsPermissionReallyDirty(permission) ? 'dirty-row' : ''\">\n <td class=\"permission-left-col\" (click)=\"flipRow(permission)\">\n @if(this.Mode === 'Entity') {\n <span>\n {{ getRoleName(permission.RoleID) }}\n </span>\n }\n @else if(this.Mode === 'Role') {\n <span>\n {{ permission.Entity }}\n </span>\n }\n @if(IsPermissionReallyDirty(permission)) {\n <span class=\"fa-solid fa-arrow-rotate-left\" (click)=\"revertRow($event, permission)\"></span>\n }\n </td>\n <td (click)=\"flipPermission($event, permission, 'Read', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanRead\" (click)=\"flipPermission($event, permission, 'Read', false)\">\n </td>\n <td (click)=\"flipPermission($event, permission, 'Create', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanCreate\" (click)=\"flipPermission($event, permission, 'Create', false)\">\n </td>\n <td (click)=\"flipPermission($event, permission, 'Update', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanUpdate\" (click)=\"flipPermission($event, permission, 'Update', false)\">\n </td>\n <td (click)=\"flipPermission($event, permission, 'Delete', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanDelete\" (click)=\"flipPermission($event, permission, 'Delete', false)\">\n </td>\n </tr>\n }\n </tbody>\n </table>\n}\n</div>\n"]}
1
+ {"version":3,"file":"entity-permissions-grid.component.js","sourceRoot":"","sources":["../../../src/lib/grid/entity-permissions-grid.component.ts","../../../src/lib/grid/entity-permissions-grid.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Output, EventEmitter, OnInit, Input, SimpleChanges, OnChanges } from '@angular/core';\n\nimport { Metadata, RunView } from '@memberjunction/core';\nimport { MJEntityPermissionEntity } from '@memberjunction/core-entities';\nimport { UUIDsEqual, NormalizeUUID } from '@memberjunction/global';\n\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nexport type EntityPermissionChangedEvent = {\n EntityName: string,\n RoleID: string\n PermissionTypeChanged: 'Read' | 'Create' | 'Update' | 'Delete'\n Value: boolean\n Cancel: boolean\n}\n \n@Component({\n standalone: false,\n selector: 'mj-entity-permissions-grid',\n templateUrl: './entity-permissions-grid.component.html',\n styleUrls: ['./entity-permissions-grid.component.css']\n})\nexport class EntityPermissionsGridComponent extends BaseAngularComponent implements OnInit, OnChanges {\n @Input() Mode: 'Entity' | 'Role' = 'Entity';\n @Input() EntityName!: string; // used when Mode is 'Entity'\n @Input() RoleName!: string; // used when Mode is 'Role'\n @Input() BottomMargin: number = 0;\n\n @Output() PermissionChanged = new EventEmitter<EntityPermissionChangedEvent>();\n\n public permissions: MJEntityPermissionEntity[] = [];\n public gridHeight: number = 750;\n public isLoading: boolean = false;\n\n /**\n * Role-name lookup keyed by NormalizeUUID(roleID). Built once when roles are\n * available (data load) so {@link getRoleName} is an O(1) Map read per table\n * row instead of an O(roles) UUIDsEqual scan over `md.Roles` every CD cycle.\n */\n private roleNameMap = new Map<string, string>();\n\n constructor() {\n super();\n }\n\n ngOnInit(): void {\n this.Refresh()\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes.EntityName && !changes.EntityName.isFirstChange()) {\n // If EntityName has changed and it's not the first change (initialization)\n this.Refresh();\n }\n }\n\n ValidateInputs() {\n if (this.Mode === 'Entity' && (!this.EntityName || this.EntityName.length === 0))\n throw new Error(\"EntityName is required when Mode is 'Entity'\")\n if (this.Mode === 'Role' && (!this.RoleName || this.RoleName.length === 0))\n throw new Error(\"RoleName is required when Mode is 'Role'\") \n }\n async Refresh() { \n this.ValidateInputs();\n const startTime = new Date().getTime();\n this.isLoading = true\n\n const md = this.ProviderToUse;\n this.buildRoleNameMap();\n const entity = md.Entities.find(e => e.Name === this.EntityName);\n if (this.Mode === 'Entity' && !entity)\n throw new Error(\"Entity not found: \" + this.EntityName)\n\n const r = md.Roles.find(r => r.Name === this.RoleName);\n if (this.Mode === 'Role' && !r)\n throw new Error(\"Role not found: \" + this.RoleName)\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const filter: string = this.Mode === 'Entity' ? `EntityID='${entity!.ID}'` : `RoleName='${r?.Name}'`;\n const result = await rv.RunView({\n EntityName: 'MJ: Entity Permissions',\n ExtraFilter: filter,\n OrderBy: 'RoleName ASC, Entity ASC',\n ResultType: 'entity_object'\n })\n if (result.Success) {\n // we have all of the saved permissions now\n // the post-process we need to do now is to see if there are any roles that don't have any existing permissions and if so, we need to create \n // new permission records for them. We won't actually consider those \"Dirty\" and save those unless the user actually selects one or more\n // to turn on, we are just doing this to make the grid easy to manage from the user perspective.\n const existingPermissions = <MJEntityPermissionEntity[]>result.Results;\n const roles = md.Roles;\n\n if (this.Mode === 'Entity') {\n const rolesWithNoPermissions = roles.filter(r => !existingPermissions.some(p => UUIDsEqual(p.RoleID, r.ID)));\n for (const r of rolesWithNoPermissions) {\n const p = await md.GetEntityObject<MJEntityPermissionEntity>('MJ: Entity Permissions')\n \n await p.LoadFromData({\n ID: null,\n Entity: entity!.Name,\n EntityID: entity!.ID,\n RoleID: r!.ID,\n CanRead: false,\n CanCreate: false,\n CanUpdate: false,\n CanDelete: false\n });\n existingPermissions.push(p);\n }\n this.permissions = existingPermissions;\n }\n else if (this.Mode === 'Role') {\n // for the mode of Role, that means we want to show all entities and their permissions for the given role\n const entitiesWithNoPermissions = md.Entities.filter(e => !existingPermissions.some(p => UUIDsEqual(p.EntityID, e.ID)));\n for (const e of entitiesWithNoPermissions) {\n const p = await md.GetEntityObject<MJEntityPermissionEntity>('MJ: Entity Permissions')\n \n await p.LoadFromData({\n ID: null,\n Entity: e.Name,\n EntityID: e.ID,\n RoleName: r!.Name,\n CanRead: false,\n CanCreate: false,\n CanUpdate: false,\n CanDelete: false\n });\n existingPermissions.push(p);\n }\n this.permissions = existingPermissions.sort((a, b) => a.Entity!.localeCompare(b.Entity!)); \n }\n }\n else {\n throw new Error(\"Error loading entity permissions: \" + result.ErrorMessage)\n }\n this.isLoading = false\n }\n \n\n /**\n * Populates {@link roleNameMap} from the provider's role list. Called on data\n * load so the per-row template binding {@link getRoleName} never scans\n * `md.Roles` during change detection.\n */\n private buildRoleNameMap(): void {\n const md = this.ProviderToUse;\n this.roleNameMap.clear();\n for (const r of md.Roles) {\n this.roleNameMap.set(NormalizeUUID(r.ID), r.Name);\n }\n }\n\n public getRoleName(roleID: string): string {\n if (!roleID) return '';\n return this.roleNameMap.get(NormalizeUUID(roleID)) ?? '';\n }\n \n public async savePermissions() {\n if (this.NumDirtyPermissions > 0) {\n // iterate through each permisison and for the ones that are dirty, add to transaction group then commit at once\n const md = this.ProviderToUse;\n const tg = await md.CreateTransactionGroup();\n let itemCount: number = 0;\n for (const p of this.permissions) {\n if (this.IsPermissionReallyDirty(p)) {\n p.TransactionGroup = tg;\n itemCount++;\n await p.Save(); \n }\n }\n if (itemCount > 0)\n await tg.Submit();\n }\n }\n\n public async cancelEdit() {\n if (this.NumDirtyPermissions > 0) {\n // go through and revert each permission that is REALLY dirty\n this.permissions.forEach(p => {\n if (this.IsPermissionReallyDirty(p)) {\n p.Revert();\n }\n })\n }\n }\n\n public get NumDirtyPermissions(): number {\n return this.permissions.filter(p => this.IsPermissionReallyDirty(p)).length;\n }\n\n protected IsPermissionReallyDirty(p: MJEntityPermissionEntity): boolean {\n if (!p.Dirty)\n return false;\n else if (p.IsSaved)\n return true;\n else\n return p.CanRead || p.CanCreate || p.CanUpdate || p.CanDelete; // if we have a new record, only consider it dirty if at least one permission is true\n }\n\n public flipAllPermissions(type: 'Read' | 'Create' | 'Update' | 'Delete') {\n // first, figure out what we have the majority of, if we have more ON, then we will flip to OFF, otherwise we will flip to ON\n let onCount = 0;\n let offCount = 0;\n this.permissions.forEach(p => {\n if (type === 'Read') {\n if (p.CanRead)\n onCount++;\n else\n offCount++;\n }\n else if (type === 'Create') {\n if (p.CanCreate)\n onCount++;\n else\n offCount++;\n }\n else if (type === 'Update') {\n if (p.CanUpdate)\n onCount++;\n else\n offCount++;\n }\n else if (type === 'Delete') {\n if (p.CanDelete)\n onCount++;\n else\n offCount++;\n }\n })\n const value = offCount > onCount;\n\n // now set the permission for each permission record\n for (const p of this.permissions) {\n switch (type) {\n case 'Read':\n p.CanRead = value;\n break;\n case 'Create':\n p.CanCreate = value;\n break;\n case 'Update':\n p.CanUpdate = value;\n break;\n case 'Delete':\n p.CanDelete = value;\n break;\n }\n this.flipPermission(undefined, p, type, false); // call this function but tell it to NOT actually flip the permission, just to fire the event\n }\n }\n\n public revertRow(event: MouseEvent, permission: MJEntityPermissionEntity) {\n if (this.IsPermissionReallyDirty(permission)) {\n permission.Revert();\n event.stopPropagation(); // don't bubble up to the parent row because that will do something else...\n }\n }\n\n public flipRow(permission: MJEntityPermissionEntity) {\n // if 2 or more are on, flip all to off, otherwise flip all to on\n const onCount = (permission.CanRead ? 1 : 0) + (permission.CanCreate ? 1 : 0) + (permission.CanUpdate ? 1 : 0) + (permission.CanDelete ? 1 : 0);\n const newValue = onCount < 2;\n\n if (permission.CanRead !== newValue) {\n permission.CanRead = newValue;\n this.flipPermission(undefined, permission, 'Read', false); // fire the event but don't actually flip the permission\n }\n\n if (permission.CanCreate !== newValue) {\n permission.CanCreate = newValue;\n this.flipPermission(undefined, permission, 'Create', false); // fire the event but don't actually flip the permission\n }\n\n if (permission.CanUpdate !== newValue) {\n permission.CanUpdate = newValue;\n this.flipPermission(undefined, permission, 'Update', false); // fire the event but don't actually flip the permission\n }\n\n if (permission.CanDelete !== newValue) {\n permission.CanDelete = newValue;\n this.flipPermission(undefined, permission, 'Delete', false); // fire the event but don't actually flip the permission\n }\n }\n\n public flipPermission(event: MouseEvent | undefined, permission: MJEntityPermissionEntity, type: 'Read' | 'Create' | 'Update' | 'Delete', flipPermission: boolean) {\n if (flipPermission) {\n switch (type) {\n case 'Read':\n permission.CanRead = !permission.CanRead;\n break;\n case 'Create':\n permission.CanCreate = !permission.CanCreate;\n break;\n case 'Update':\n permission.CanUpdate = !permission.CanUpdate;\n break;\n case 'Delete':\n permission.CanDelete = !permission.CanDelete;\n break;\n }\n }\n // always fire the event\n const value = type === 'Read' ? permission.CanRead : type === 'Create' ? permission.CanCreate : type === 'Update' ? permission.CanUpdate : permission.CanDelete;\n this.PermissionChanged.emit({\n EntityName: this.EntityName,\n RoleID: permission.RoleID!,\n PermissionTypeChanged: type,\n Value: value,\n Cancel: false\n })\n\n if (!flipPermission && event)\n event.stopPropagation();\n }\n}\n","<div>\n @if (isLoading) {\n <div><mj-loading [showText]=\"false\" size=\"medium\"></mj-loading></div>\n }\n <button [disabled]=\"NumDirtyPermissions === 0\" mjButton (click)=\"savePermissions()\">Save</button>\n <button [disabled]=\"NumDirtyPermissions === 0\" mjButton (click)=\"cancelEdit()\">Cancel</button>\n @if (!isLoading) {\n <table class=\"grid\">\n <thead>\n <tr>\n @if (this.Mode==='Entity') {\n <th>Role</th>\n }\n @if (this.Mode==='Role') {\n <th>Entity</th>\n }\n <th (click)=\"flipAllPermissions('Read')\">Read</th>\n <th (click)=\"flipAllPermissions('Create')\">Create</th>\n <th (click)=\"flipAllPermissions('Update')\">Update</th>\n <th (click)=\"flipAllPermissions('Delete')\">Delete</th>\n </tr>\n </thead>\n <tbody>\n @for (permission of permissions; track permission) {\n <tr [ngClass]=\"IsPermissionReallyDirty(permission) ? 'dirty-row' : ''\">\n <td class=\"permission-left-col\" (click)=\"flipRow(permission)\">\n @if(this.Mode === 'Entity') {\n <span>\n {{ getRoleName(permission.RoleID) }}\n </span>\n }\n @else if(this.Mode === 'Role') {\n <span>\n {{ permission.Entity }}\n </span>\n }\n @if(IsPermissionReallyDirty(permission)) {\n <span class=\"fa-solid fa-arrow-rotate-left\" (click)=\"revertRow($event, permission)\"></span>\n }\n </td>\n <td (click)=\"flipPermission($event, permission, 'Read', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanRead\" (click)=\"flipPermission($event, permission, 'Read', false)\">\n </td>\n <td (click)=\"flipPermission($event, permission, 'Create', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanCreate\" (click)=\"flipPermission($event, permission, 'Create', false)\">\n </td>\n <td (click)=\"flipPermission($event, permission, 'Update', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanUpdate\" (click)=\"flipPermission($event, permission, 'Update', false)\">\n </td>\n <td (click)=\"flipPermission($event, permission, 'Delete', true)\">\n <input type=\"checkbox\" [(ngModel)]=\"permission.CanDelete\" (click)=\"flipPermission($event, permission, 'Delete', false)\">\n </td>\n </tr>\n }\n </tbody>\n </table>\n}\n</div>\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-entity-permissions",
3
- "version": "5.40.2",
3
+ "version": "5.41.0",
4
4
  "description": "MemberJunction: Angular components for displaying/editing permissions for an entity, and related components.",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -8,7 +8,7 @@
8
8
  "/dist"
9
9
  ],
10
10
  "scripts": {
11
- "test": "echo \"No tests configured yet\"",
11
+ "test": "vitest run",
12
12
  "build": "ngc"
13
13
  },
14
14
  "keywords": [],
@@ -25,14 +25,14 @@
25
25
  "@angular/router": "21.1.3"
26
26
  },
27
27
  "dependencies": {
28
- "@memberjunction/core": "5.40.2",
29
- "@memberjunction/core-entities": "5.40.2",
30
- "@memberjunction/global": "5.40.2",
31
- "@memberjunction/ng-base-types": "5.40.2",
32
- "@memberjunction/ng-container-directives": "5.40.2",
33
- "@memberjunction/ng-shared": "5.40.2",
34
- "@memberjunction/ng-shared-generic": "5.40.2",
35
- "@memberjunction/ng-ui-components": "5.40.2",
28
+ "@memberjunction/core": "5.41.0",
29
+ "@memberjunction/core-entities": "5.41.0",
30
+ "@memberjunction/global": "5.41.0",
31
+ "@memberjunction/ng-base-types": "5.41.0",
32
+ "@memberjunction/ng-container-directives": "5.41.0",
33
+ "@memberjunction/ng-shared": "5.41.0",
34
+ "@memberjunction/ng-shared-generic": "5.41.0",
35
+ "@memberjunction/ng-ui-components": "5.41.0",
36
36
  "tslib": "^2.8.1"
37
37
  },
38
38
  "sideEffects": false,