diff options
author | Jamie V <jamie.j.vigliotta@nasa.gov> | 2022-09-30 23:47:10 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-30 23:47:10 +0300 |
commit | 8c92178895a5ba237e6e2e011630767bd3535a68 (patch) | |
tree | 8f8bc90b96fd8e99f3cd7dc46d370630dd77cd3d | |
parent | 35bbebbbc74239abccf94d32048f46b25eae8d90 (diff) |
[User Attribution] "createdBy" and "modifiedBy" fields for domainObjects (#5741)
* Implementation of user attribution of object changes
* Adds created date to object creation
* Updating remove action to wait for save before navigationg
Co-authored-by: Andrew Henry <akhenry@gmail.com>
-rw-r--r-- | src/api/annotation/AnnotationAPISpec.js | 1 | ||||
-rw-r--r-- | src/api/objects/ObjectAPI.js | 18 | ||||
-rw-r--r-- | src/api/objects/ObjectAPISpec.js | 35 | ||||
-rw-r--r-- | src/plugins/plan/pluginSpec.js | 2 | ||||
-rw-r--r-- | src/plugins/remove/RemoveAction.js | 8 | ||||
-rw-r--r-- | src/ui/inspector/InspectorDetailsSpec.js | 12 | ||||
-rw-r--r-- | src/ui/inspector/details/Properties.vue | 36 |
7 files changed, 95 insertions, 17 deletions
diff --git a/src/api/annotation/AnnotationAPISpec.js b/src/api/annotation/AnnotationAPISpec.js index a7e2a162d..154369a84 100644 --- a/src/api/annotation/AnnotationAPISpec.js +++ b/src/api/annotation/AnnotationAPISpec.js @@ -94,7 +94,6 @@ describe("The Annotation API", () => { openmct.startHeadless(); }); afterEach(async () => { - openmct.objects.providers = {}; await resetApplicationState(openmct); }); it("is defined", () => { diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js index 1d0ccfe25..218f1018e 100644 --- a/src/api/objects/ObjectAPI.js +++ b/src/api/objects/ObjectAPI.js @@ -96,7 +96,7 @@ export default class ObjectAPI { this.cache = {}; this.interceptorRegistry = new InterceptorRegistry(); - this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan', 'annotation']; + this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'restricted-notebook', 'plan', 'annotation']; this.errors = { Conflict: ConflictError @@ -354,7 +354,7 @@ export default class ObjectAPI { * @returns {Promise} a promise which will resolve when the domain object * has been saved, or be rejected if it cannot be saved */ - save(domainObject) { + async save(domainObject) { let provider = this.getProvider(domainObject.identifier); let savedResolve; let savedReject; @@ -372,6 +372,8 @@ export default class ObjectAPI { savedReject = reject; }); domainObject.persisted = persistedTime; + domainObject.created = persistedTime; + domainObject.createdBy = await this.#getCurrentUsername(); const newObjectPromise = provider.create(domainObject); if (newObjectPromise) { newObjectPromise.then(response => { @@ -385,6 +387,7 @@ export default class ObjectAPI { } } else { domainObject.persisted = persistedTime; + domainObject.modifiedBy = await this.#getCurrentUsername(); this.mutate(domainObject, 'persisted', persistedTime); result = provider.update(domainObject); } @@ -399,6 +402,17 @@ export default class ObjectAPI { }); } + async #getCurrentUsername() { + const user = await this.openmct.user.getCurrentUser(); + let username; + + if (user !== undefined) { + username = user.getName(); + } + + return username; + } + /** * After entering into edit mode, creates a new instance of Transaction to keep track of changes in Objects */ diff --git a/src/api/objects/ObjectAPISpec.js b/src/api/objects/ObjectAPISpec.js index 950b356be..344c67d10 100644 --- a/src/api/objects/ObjectAPISpec.js +++ b/src/api/objects/ObjectAPISpec.js @@ -8,13 +8,27 @@ describe("The Object API", () => { let mockDomainObject; const TEST_NAMESPACE = "test-namespace"; const TEST_KEY = "test-key"; + const USERNAME = 'Joan Q Public'; const FIFTEEN_MINUTES = 15 * 60 * 1000; beforeEach((done) => { typeRegistry = jasmine.createSpyObj('typeRegistry', [ 'get' ]); + const userProvider = { + isLoggedIn() { + return true; + }, + getCurrentUser() { + return Promise.resolve({ + getName() { + return USERNAME; + } + }); + } + }; openmct = createOpenMct(); + openmct.user.setProvider(userProvider); objectAPI = openmct.objects; openmct.editor = {}; @@ -63,19 +77,34 @@ describe("The Object API", () => { mockProvider.update.and.returnValue(Promise.resolve(true)); objectAPI.addProvider(TEST_NAMESPACE, mockProvider); }); - it("Calls 'create' on provider if object is new", () => { + it("Adds a 'created' timestamp to new objects", () => { objectAPI.save(mockDomainObject); + expect(mockDomainObject.created).not.toBeUndefined(); + }); + it("Calls 'create' on provider if object is new", async () => { + await objectAPI.save(mockDomainObject); expect(mockProvider.create).toHaveBeenCalled(); expect(mockProvider.update).not.toHaveBeenCalled(); }); - it("Calls 'update' on provider if object is not new", () => { + it("Calls 'update' on provider if object is not new", async () => { mockDomainObject.persisted = Date.now() - FIFTEEN_MINUTES; mockDomainObject.modified = Date.now(); - objectAPI.save(mockDomainObject); + await objectAPI.save(mockDomainObject); expect(mockProvider.create).not.toHaveBeenCalled(); expect(mockProvider.update).toHaveBeenCalled(); }); + it("Sets the current user for 'createdBy' on new objects", async () => { + await objectAPI.save(mockDomainObject); + expect(mockDomainObject.createdBy).toBe(USERNAME); + }); + it("Sets the current user for 'modifedBy' on existing objects", async () => { + mockDomainObject.persisted = Date.now() - FIFTEEN_MINUTES; + mockDomainObject.modified = Date.now(); + + await objectAPI.save(mockDomainObject); + expect(mockDomainObject.modifiedBy).toBe(USERNAME); + }); it("Does not persist if the object is unchanged", () => { mockDomainObject.persisted = diff --git a/src/plugins/plan/pluginSpec.js b/src/plugins/plan/pluginSpec.js index d981ac265..781ae7710 100644 --- a/src/plugins/plan/pluginSpec.js +++ b/src/plugins/plan/pluginSpec.js @@ -264,7 +264,7 @@ describe('the plugin', function () { it('provides an inspector view with the version information if available', () => { componentObject = component.$root.$children[0]; const propertiesEls = componentObject.$el.querySelectorAll('.c-inspect-properties__row'); - expect(propertiesEls.length).toEqual(4); + expect(propertiesEls.length).toEqual(6); const found = Array.from(propertiesEls).some((propertyEl) => { return (propertyEl.children[0].innerHTML.trim() === 'Version' && propertyEl.children[1].innerHTML.trim() === 'v1'); diff --git a/src/plugins/remove/RemoveAction.js b/src/plugins/remove/RemoveAction.js index 8a0b9ec4c..c037f9984 100644 --- a/src/plugins/remove/RemoveAction.js +++ b/src/plugins/remove/RemoveAction.js @@ -34,8 +34,8 @@ export default class RemoveAction { invoke(objectPath) { let object = objectPath[0]; let parent = objectPath[1]; - this.showConfirmDialog(object).then(() => { - this.removeFromComposition(parent, object); + this.showConfirmDialog(object).then(async () => { + await this.removeFromComposition(parent, object); if (this.inNavigationPath(object)) { this.navigateTo(objectPath.slice(1)); } @@ -81,7 +81,7 @@ export default class RemoveAction { this.openmct.router.navigate('#/browse/' + urlPath); } - removeFromComposition(parent, child) { + async removeFromComposition(parent, child) { let composition = parent.composition.filter(id => !this.openmct.objects.areIdsEqual(id, child.identifier) ); @@ -93,7 +93,7 @@ export default class RemoveAction { } if (!this.isAlias(child, parent)) { - this.openmct.objects.mutate(child, 'location', null); + await this.openmct.objects.mutate(child, 'location', null); } } diff --git a/src/ui/inspector/InspectorDetailsSpec.js b/src/ui/inspector/InspectorDetailsSpec.js index 24553c5c4..208069fb0 100644 --- a/src/ui/inspector/InspectorDetailsSpec.js +++ b/src/ui/inspector/InspectorDetailsSpec.js @@ -38,6 +38,8 @@ describe('the inspector', () => { folderItem = { name: 'folder', type: 'folder', + createdBy: 'John Q', + modifiedBy: 'Public', id: 'mock-folder-key', identifier: { namespace: '', @@ -74,6 +76,8 @@ describe('the inspector', () => { const [ title, type, + createdBy, + modifiedBy, notes, timestamp ] = details; @@ -87,6 +91,14 @@ describe('the inspector', () => { .toEqual('Type'); expect(type.value.toLowerCase()) .toEqual(folderItem.type); + expect(createdBy.name) + .toEqual('Created By'); + expect(createdBy.value) + .toEqual(folderItem.createdBy); + expect(modifiedBy.name) + .toEqual('Modified By'); + expect(modifiedBy.value) + .toEqual(folderItem.modifiedBy); expect(notes.value) .toEqual('This object should have some notes'); diff --git a/src/ui/inspector/details/Properties.vue b/src/ui/inspector/details/Properties.vue index fe0edda9e..d14a2b4ed 100644 --- a/src/ui/inspector/details/Properties.vue +++ b/src/ui/inspector/details/Properties.vue @@ -90,10 +90,13 @@ export default { return; } + const UNKNOWN_USER = 'Unknown'; const title = this.domainObject.name; const typeName = this.type ? this.type.definition.name : `Unknown: ${this.domainObject.type}`; - const timestampLabel = this.domainObject.modified ? 'Modified' : 'Created'; - const timestamp = this.domainObject.modified ? this.domainObject.modified : this.domainObject.created; + const createdTimestamp = this.domainObject.created; + const createdBy = this.domainObject.createdBy ? this.domainObject.createdBy : UNKNOWN_USER; + const modifiedBy = this.domainObject.modifiedBy ? this.domainObject.modifiedBy : UNKNOWN_USER; + const modifiedTimestamp = this.domainObject.modified ? this.domainObject.modified : this.domainObject.created; const notes = this.domainObject.notes; const version = this.domainObject.version; @@ -105,6 +108,14 @@ export default { { name: 'Type', value: typeName + }, + { + name: 'Created By', + value: createdBy + }, + { + name: 'Modified By', + value: modifiedBy } ]; @@ -115,15 +126,28 @@ export default { }); } - if (timestamp !== undefined) { - const formattedTimestamp = Moment.utc(timestamp) + if (createdTimestamp !== undefined) { + const formattedCreatedTimestamp = Moment.utc(createdTimestamp) + .format('YYYY-MM-DD[\n]HH:mm:ss') + + ' UTC'; + + details.push( + { + name: 'Created', + value: formattedCreatedTimestamp + } + ); + } + + if (modifiedTimestamp !== undefined) { + const formattedModifiedTimestamp = Moment.utc(modifiedTimestamp) .format('YYYY-MM-DD[\n]HH:mm:ss') + ' UTC'; details.push( { - name: timestampLabel, - value: formattedTimestamp + name: 'Modified', + value: formattedModifiedTimestamp } ); } |