Skip to content

Commit

Permalink
Add Edit Profile Info to Results View (#1169)
Browse files Browse the repository at this point in the history
* Add evaluation tags to EvaluationInfo, sort evaluation info cards by date

* Move Profile information into ProfileInfo, start converting ContextualiedProfile into ProfileFile within Results.vue

* Cleanup profile info

Standardize format of files passed between components (most are SourcedContextualizedProfiles | SourcedContextualizedEvaluation now)
Remove unused functions and files

* Fix Sonarcloud errors

* Only update evaluations if in server mode, remove extra margin

* Add EditEvaluationModal button to Results.vue

* Remove console.log

* Log uncaught exceptions for database-results, and return False

* Exclusively ignore getBoundingClientRect errors

* Remove call to getAllEvaluations, remove duplicate uncaught:exception handling, use Array.find instead of Array.forEach

* Add abac guard editable permission to create evaluation test

* Lint evaluations controller

* Remove authzService from evaluation controller and instead hardcode the expected value

* Automatically update filename when users edit files which are currently loaded

* Cypress within does not properly handle when the nested element redraws, call the whole path at once instead.

Co-authored-by: Robert Clark <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Apr 20, 2021
1 parent dcf9136 commit ac4d1e8
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 47 deletions.
8 changes: 7 additions & 1 deletion apps/backend/src/evaluations/evaluations.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@ describe('EvaluationsController', () => {
evaluation.id,
{user: user}
);
expect(foundEvaluation).toEqual(new EvaluationDto(evaluation));

expect(foundEvaluation).toEqual(
// The evaluation is created with the current user ID above
// so the expectation is that user should be able to edit
// which is the 2nd parameter to EvaluationDto.
new EvaluationDto(evaluation, true)
);
});

it('should return an evaluations tags', async () => {
Expand Down
10 changes: 8 additions & 2 deletions apps/backend/src/evaluations/evaluations.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class EvaluationsController {
const abac = this.authz.abac.createForUser(request.user);
const evaluation = await this.evaluationsService.findById(id);
ForbiddenError.from(abac).throwUnlessCan(Action.Read, evaluation);
return new EvaluationDto(evaluation);
return new EvaluationDto(evaluation, abac.can(Action.Update, evaluation));
}

@Get(':id/groups')
Expand Down Expand Up @@ -126,8 +126,14 @@ export class EvaluationsController {
const evaluationToUpdate = await this.evaluationsService.findById(id);
ForbiddenError.from(abac).throwUnlessCan(Action.Update, evaluationToUpdate);

const updatedEvaluation = await this.evaluationsService.update(
id,
updateEvaluationDto
);

return new EvaluationDto(
await this.evaluationsService.update(id, updateEvaluationDto)
updatedEvaluation,
abac.can(Action.Update, updatedEvaluation)
);
}

Expand Down
12 changes: 5 additions & 7 deletions apps/frontend/src/components/cards/EvaluationInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export default class EvaluationInfo extends Vue {
}
get filename(): string {
return this.file_object.filename;
// Filename can be updated by a user when filename is loaded from the database. This causes any changes
// to the name to show up right away.
return this.evaluation?.filename || this.file_object.filename;
}
get inspec_version(): string | undefined {
Expand All @@ -55,13 +57,9 @@ export default class EvaluationInfo extends Vue {
}
get evaluation(): IEvaluation | undefined {
let result: IEvaluation | undefined;
EvaluationModule.allEvaluations.forEach((e) => {
if(e.id === this.file_object.database_id?.toString()) {
result = e
}
return EvaluationModule.allEvaluations.find((e) => {
return e.id === this.file_object.database_id?.toString()
})
return result
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ import {Prop} from 'vue-property-decorator';
import {ICreateEvaluation} from '@heimdall/interfaces';
import _ from 'lodash';
import RouteMixin from '@/mixins/RouteMixin';
import {EvaluationModule} from '@/store/evaluations';
@Component
export default class FileItem extends mixins(ServerMixin, RouteMixin) {
@Prop({type: Object}) readonly file!: EvaluationFile | ProfileFile;
saving: boolean = false;
saving = false;
select_file() {
if (this.file.hasOwnProperty('evaluation')) {
Expand Down Expand Up @@ -119,14 +121,16 @@ export default class FileItem extends mixins(ServerMixin, RouteMixin) {
.then((response) => {
SnackbarModule.notify('File saved successfully');
file.database_id = parseInt(response.data.id);
EvaluationModule.loadEvaluation(response.data.id);
const loadedDatabaseIds = InspecDataModule.loadedDatabaseIds.join(',');
this.navigateWithNoErrors(`/${this.current_route}/${loadedDatabaseIds}`);
})
.catch((error) => {
SnackbarModule.failure(error.response.data.message);
}).finally(() => {
this.saving = false;
});
}
//gives different icons for a file if it is just a profile
get icon(): string {
if (this.file.hasOwnProperty('profile')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ export default class EditEvaluationModal extends Vue {
async update(): Promise<void> {
Promise.all([EvaluationModule.updateEvaluation(this.activeEvaluation), this.updateGroups()]).then(() => {
SnackbarModule.notify('Evaluation Updated Successfully');
this.$emit('updateEvaluations')
})
this.visible = false;
}
Expand Down
45 changes: 31 additions & 14 deletions apps/frontend/src/store/evaluations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,14 @@ export class Evaluation extends VuexModule {
);
return Promise.all(
unloadedIds.map(async (id) => {
return axios
.get<IEvaluation>(`/evaluations/${id}`)
.then(async ({data}) => {
this.addEvaluation(data);
return this.loadEvaluation(id)
.then(async (evaluation) => {
return InspecIntakeModule.loadText({
text: JSON.stringify(data.data),
filename: data.filename,
database_id: data.id,
createdAt: data.createdAt,
updatedAt: data.updatedAt,
text: JSON.stringify(evaluation.data),
filename: evaluation.filename,
database_id: evaluation.id,
createdAt: evaluation.createdAt,
updatedAt: evaluation.updatedAt,
tags: [] // Tags are not yet implemented, so for now the value is passed in empty
}).catch((err) => {
SnackbarModule.failure(err);
Expand All @@ -70,13 +68,21 @@ export class Evaluation extends VuexModule {
}

@Action
async addEvaluation(evaluation: IEvaluation) {
this.context.commit('ADD_EVALUATION', evaluation);
async loadEvaluation(id: string) {
return axios.get<IEvaluation>(`/evaluations/${id}`).then(({data}) => {
this.context.commit('SAVE_EVALUTION', data);
return data;
});
}

@Action
async updateEvaluation(evaluation: IEvaluation) {
return axios.put(`/evaluations/${evaluation.id}`, evaluation);
return axios
.put<IEvaluation>(`/evaluations/${evaluation.id}`, evaluation)
.then(({data}) => {
this.context.commit('SAVE_EVALUTION', data);
return data;
});
}

@Action
Expand Down Expand Up @@ -106,9 +112,20 @@ export class Evaluation extends VuexModule {
this.allEvaluations = evaluations;
}

// Save an evaluation or update it if it is already saved.
@Mutation
ADD_EVALUATION(evaluation: IEvaluation) {
this.allEvaluations.push(evaluation);
SAVE_EVALUTION(evaluationToSave: IEvaluation) {
let found = false;
for (const [index, evaluation] of this.allEvaluations.entries()) {
if (evaluationToSave.id === evaluation.id) {
this.allEvaluations.splice(index, 1, evaluationToSave);
found = true;
break;
}
}
if (!found) {
this.allEvaluations.push(evaluationToSave);
}
}

@Mutation
Expand Down
66 changes: 57 additions & 9 deletions apps/frontend/src/views/Results.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,32 @@
@click="toggle_profile(file)"
>
<EvaluationInfo :file="file" />
<v-card-subtitle
style="position: absolute; bottom: 0; right: 0"
>
<v-card-actions>
<div
v-if="
file.from_file.database_id &&
getDbFile(file.from_file).editable
"
class="top-right"
>
<EditEvaluationModal
id="editEvaluationModal"
:active="getDbFile(file.from_file)"
>
<template #clickable="{on}"
><v-icon
data-cy="edit"
title="Edit"
class="mr-3 mt-3"
v-on="on"
>
mdi-pencil
</v-icon>
</template>
</EditEvaluationModal>
</div>
</v-card-actions>
<v-card-subtitle class="bottom-right">
File Info ↓
</v-card-subtitle>
</v-card>
Expand Down Expand Up @@ -198,6 +221,7 @@ import StatusChart from '@/components/cards/StatusChart.vue';
import SeverityChart from '@/components/cards/SeverityChart.vue';
import ComplianceChart from '@/components/cards/ComplianceChart.vue';
import UploadButton from '@/components/generic/UploadButton.vue';
import EditEvaluationModal from '@/components/global/upload_tabs/EditEvaluationModal.vue';
import ExportCaat from '@/components/global/ExportCaat.vue';
import ExportNist from '@/components/global/ExportNist.vue';
Expand All @@ -206,17 +230,19 @@ import EvaluationInfo from '@/components/cards/EvaluationInfo.vue';
import {FilteredDataModule, Filter, TreeMapState} from '@/store/data_filters';
import {ControlStatus, Severity} from 'inspecjs';
import {FileID, SourcedContextualizedEvaluation, SourcedContextualizedProfile} from '@/store/report_intake';
import {EvaluationFile, FileID, ProfileFile, SourcedContextualizedEvaluation, SourcedContextualizedProfile} from '@/store/report_intake';
import {InspecDataModule} from '@/store/data_store';
import ProfileData from '@/components/cards/ProfileData.vue';
import {ServerModule} from '@/store/server';
import {capitalize} from 'lodash';
import {compare_times} from '../utilities/delta_util';
import {EvaluationModule} from '../store/evaluations';
import RouteMixin from '@/mixins/RouteMixin';
import {StatusCountModule} from '../store/status_counts';
import ServerMixin from '../mixins/ServerMixin';
import {IEvaluation} from '@heimdall/interfaces';
@Component({
components: {
Expand All @@ -232,7 +258,8 @@ import ServerMixin from '../mixins/ServerMixin';
ExportJson,
EvaluationInfo,
ProfileData,
UploadButton
UploadButton,
EditEvaluationModal
}
})
export default class Results extends mixins(RouteMixin, ServerMixin) {
Expand Down Expand Up @@ -295,6 +322,18 @@ export default class Results extends mixins(RouteMixin, ServerMixin) {
return this.is_result_view ? this.evaluationFiles : this.profiles;
}
getFile(fileID: FileID) {
return InspecDataModule.allFiles.find(
(f) => f.unique_id === fileID
);
}
getDbFile(file: EvaluationFile | ProfileFile): IEvaluation | undefined {
return EvaluationModule.allEvaluations.find((e) => {
return e.id === file.database_id?.toString()
})
}
/**
* Returns true if we're showing results
*/
Expand Down Expand Up @@ -400,11 +439,10 @@ export default class Results extends mixins(RouteMixin, ServerMixin) {
get curr_title(): string {
let returnText = `${capitalize(this.current_route_name.slice(0, -1))} View`;
if (this.file_filter.length == 1) {
let file = InspecDataModule.allFiles.find(
(f) => f.unique_id === this.file_filter[0]
);
const file = this.getFile(this.file_filter[0])
if (file) {
returnText += ` (${file.filename} selected)`;
const dbFile = this.getDbFile(file);
returnText += ` (${dbFile?.filename || file.filename} selected)`;
}
} else {
returnText += ` (${this.file_filter.length} ${this.current_route_name} selected)`;
Expand Down Expand Up @@ -446,4 +484,14 @@ export default class Results extends mixins(RouteMixin, ServerMixin) {
top: 4px;
z-index: 5;
}
.bottom-right {
position: absolute;
bottom: 0;
right: 0;
}
.top-right {
position: absolute;
top: 0;
right: 0;
}
</style>
4 changes: 0 additions & 4 deletions test/integration/database-results.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ context('Database results', () => {
describe('CRUD', () => {
it('allows a user to save a result', () => {
uploadModal.loadSample(sampleToLoad);
sidebar.open();
sidebar.save(sampleToLoad);
toastVerifier.toastTextContains('File saved successfully');
uploadModal.activate();
Expand All @@ -39,7 +38,6 @@ context('Database results', () => {

it('allows a user to load a result', () => {
uploadModal.loadSample(sampleToLoad);
sidebar.open();
sidebar.save(sampleToLoad);
sidebar.close(sampleToLoad);
uploadModal.activate();
Expand All @@ -50,7 +48,6 @@ context('Database results', () => {
it('allows a user to update a result', () => {
const updatedName = 'Updated Filename';
uploadModal.loadSample(sampleToLoad);
sidebar.open();
sidebar.save(sampleToLoad);
sidebar.close(sampleToLoad);
uploadModal.activate();
Expand All @@ -61,7 +58,6 @@ context('Database results', () => {

it('allows a user to delete a result', () => {
uploadModal.loadSample(sampleToLoad);
sidebar.open();
sidebar.save(sampleToLoad);
sidebar.close(sampleToLoad);
uploadModal.activate();
Expand Down
14 changes: 7 additions & 7 deletions test/support/components/Sidebar.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
export default class Sidebar {
open(): void {
openClose(): void {
cy.get('[data-cy=openSidebar]').click({force: true});
}

save(name: string): void {
cy.get(`[title="${name}"]`).within(() => {
cy.get('[data-cy=saveFile]').click();
});
this.openClose();
cy.get(`[title="${name}"] [data-cy=saveFile]`).click();
this.openClose();
}

close(name: string): void {
cy.get(`[title="${name}"]`).within(() => {
cy.get('[data-cy=closeFile]').click();
});
this.openClose();
cy.get(`[title="${name}"] [data-cy=closeFile]`).click();
this.openClose();
}
}

0 comments on commit ac4d1e8

Please sign in to comment.