Skip to content

Commit

Permalink
refactor: rename Mapping Group to Capability Group across codebase
Browse files Browse the repository at this point in the history
Rename Mapping Group to Capability Group across codebase.

feat: add "clear search" button

feat: add "collapse/uncollapse all mapping objects" menu items

feat: add basic runtime error notifications

feat: add auto-migrate capabilities to framework objects

fix: remove "Layout" menu

fix: correct "Change Log" link

fix: remove deprecated mapping statuses

fix: deserialize mapping object `references` to a list of links (not characters) 

fix: deserialize empty strings to `null` on select mapping object fields

fix: eliminate scroll glitch that occurs when moving uncollapsed mapping objects

fix: eliminate scroll glitch that occurs when undeleting end-of-file mapping objects

perf: improve file indexing speed

perf: improve import file speed

perf: improve mass cut, copy, and paste speed

perf: improve mass delete speed
  • Loading branch information
mikecarenzo authored Feb 9, 2024
1 parent d611e1f commit 8f41917
Show file tree
Hide file tree
Showing 49 changed files with 874 additions and 395 deletions.
6 changes: 3 additions & 3 deletions src/mappings_editor/public/settings_macos.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"hotkeys": {
"file": {
"import_file": "Meta+I",
"new_file": "Meta+N",
"open_file": "Meta+O",
"import_file": "Meta+I",
"save_file": "Meta+S"
},
"edit": {
Expand All @@ -18,9 +18,9 @@
"copy": "Meta+C",
"paste": "Meta+V"
},
"layout": {
},
"view": {
"collapse_all_mappings": "Shift+C",
"uncollapse_all_mappings": "Shift+U",
"fullscreen": ""
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/mappings_editor/public/settings_win.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"hotkeys": {
"file": {
"import_file": "Control+I",
"new_file": "Control+N",
"open_file": "Control+O",
"import_file": "Control+I",
"save_file": "Control+S"
},
"edit": {
Expand All @@ -18,9 +18,9 @@
"copy": "Control+C",
"paste": "Control+V"
},
"layout": {
},
"view": {
"collapse_all_mappings": "Shift+C",
"uncollapse_all_mappings": "Shift+U",
"fullscreen": ""
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/mappings_editor/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
@execute="onExecute"
/>
</div>
<div class="frame right">
<div class="resize-handle" @pointerdown="startResize($event, Handle.Right)"></div>
</div>
<div class="frame bottom">
<AppFooterBar id="app-footer-bar"/>
</div>
Expand Down Expand Up @@ -139,6 +136,7 @@ export default defineComponent({
this.application.execute(cmd);
}
} catch(ex: any) {
alert(`Error: ${ ex.message }`)
console.error(ex);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class UniversalSchemaMappingFileSerializer extends MappingFileSerializer
creation_date : file.creation_date.toLocaleDateString("es-pa"),
last_update : file.modified_date.toLocaleDateString("es-pa"),
mapping_types : file.mapping_types,
capability_groups : file.mapping_groups
capability_groups : file.capability_groups
},
mapping_objects
}
Expand Down Expand Up @@ -101,8 +101,8 @@ export class UniversalSchemaMappingFileSerializer extends MappingFileSerializer
contact : this.maskValue(file, obj, "author_contact"),
references : obj.references,
comments : obj.comments ?? undefined,
capability_group : obj.capability_group ?? undefined,
mapping_type : obj.mapping_type,
capability_group : obj.mapping_group ?? undefined,
status : obj.mapping_status ?? undefined,
score_category : obj.score_category ?? undefined,
score_value : obj.score_value ?? undefined,
Expand Down Expand Up @@ -189,18 +189,12 @@ export class UniversalSchemaMappingFileSerializer extends MappingFileSerializer
author_organization : meta.organization,
creation_date : new Date(meta.creation_date),
modified_date : new Date(meta.last_update),
capability_groups : meta.capability_groups,
mapping_types : meta.mapping_types,
mapping_groups : meta.capability_groups,
mapping_statuses: {
"complete" : "Complete",
"in_progress" : "In Progress",
"non_mappable" : "Non-Mappable",
"Assigned" : "Assigned",
"New Mitigations" : "New Mitigations",
"Dropped Mitigations" : "Dropped Mitigations",
"New Detections" : "New Detections",
"Modified Description" : "Modified Description",
"New Techniques" : "New Techniques"
"non_mappable" : "Non-Mappable"
},
score_categories: {
"protect" : "Protect",
Expand Down Expand Up @@ -234,14 +228,14 @@ export class UniversalSchemaMappingFileSerializer extends MappingFileSerializer
if(2 <= lines.length) {
const header = lines[0];
for(let i = 1; i < lines.length; i++) {
const obj = [];
const entries = [];
for(let j = 0; j < header.length; j++) {
obj.push([
entries.push([
this.toKey(header[j]),
lines[i][j] || undefined
])
}
objs.push(Object.fromEntries(obj));
objs.push(Object.fromEntries(entries));
}
}
// Convert file
Expand Down Expand Up @@ -281,10 +275,10 @@ export class UniversalSchemaMappingFileSerializer extends MappingFileSerializer
author : this.resolveValue(file, obj, "author", null),
author_contact : this.resolveValue(file, obj, "contact", null),
author_organization : null,
references : obj.references ?? [],
references : this.parseReferences(obj.references),
comments : obj.comments ?? null,
capability_group : obj.capability_group ?? null,
mapping_type : obj.mapping_type ?? null,
mapping_group : obj.capability_group ?? null,
mapping_status : obj.status ?? "in_progress",
score_category : obj.score_category ?? null,
score_value : obj.score_value ?? null
Expand Down Expand Up @@ -330,6 +324,24 @@ export class UniversalSchemaMappingFileSerializer extends MappingFileSerializer
}
}

/**
* Pareses a set of references in the form of an array or a string of
* comma-separated values.
* @param references
* The references.
* @returns
* The parsed references.
*/
private parseReferences(references?: string | string[]): string[] {
if(Array.isArray(references)) {
return references;
}
if(typeof references === "string") {
return references.split(/,/);
}
return [];
}


///////////////////////////////////////////////////////////////////////////
// 3. String Normalization //////////////////////////////////////////////
Expand Down Expand Up @@ -412,11 +424,18 @@ type UniversalSchemaMappingFile = {
creation_date : string,
last_update : string,
mapping_types : UniversalSchemaMappingTypes,
capability_groups : UniversalSchemaMappingGroups
capability_groups : UniversalSchemaCapabilityGroups
},
mapping_objects : UniversalSchemaMappingObject[]
}

/**
* The Universal Schema Capability Groups.
*/
type UniversalSchemaCapabilityGroups = {
[key: string] : string
}

/**
* The Universal Schema Mapping Types
*/
Expand All @@ -427,13 +446,6 @@ type UniversalSchemaMappingTypes = {
}
}

/**
* The Universal Schema Mapping Groups.
*/
type UniversalSchemaMappingGroups = {
[key: string] : string
}

/**
* The Universal Schema Mapping Object Format
*/
Expand All @@ -448,7 +460,7 @@ type UniversalSchemaMappingObject = {
mapping_framework_version? : string,
author? : string,
contact? : string,
references : string[],
references : string | string[],
comments? : string,
mapping_type : string | null,
capability_group? : string,
Expand Down
2 changes: 1 addition & 1 deletion src/mappings_editor/src/assets/configuration/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const config: AppConfiguration = {
},
{
text: "Change Log",
url: "https://github.com/center-for-threat-informed-defense/cti-blueprints/blob/main/src/cti_authoring_tool/CHANGELOG.md"
url: "https://github.com/center-for-threat-informed-defense/mappings-editor/blob/main/src/mappings_editor/CHANGELOG.md"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,90 +1,90 @@
import * as EditorCommands from "@/assets/scripts/MappingFileEditor/EditorCommands";
import { AppCommand } from "../AppCommand";
import { MappingFileEditor, Reactivity } from "@/assets/scripts/MappingFileEditor";
import type { ApplicationStore } from "@/stores/ApplicationStore";
import type { MappingFile, MappingObject } from "@/assets/scripts/MappingFile";
import type { MappingFileExport } from "@/assets/scripts/MappingFileAuthority";
import { MappingFileView, MappingObjectView } from "@/assets/scripts/MappingFileEditor";
import type { MappingFileAuthority, MappingFileExport } from "@/assets/scripts/MappingFileAuthority";

export class ImportFile extends AppCommand {

/**
* The file to import.
/**
* The editor to import into.
*/
public readonly editor: MappingFileEditor;

/**
* The mapping file authority to use.
*/
private _importedFile: MappingFileExport;
public readonly fileAuthority: MappingFileAuthority;

/**
* The application context.
* The mapping file export to import.
*/
private _context: ApplicationStore;
public readonly importFile: MappingFileExport;


/**
* Loads a new mapping objects from an imported file into the application.
* Imports a mapping file export into the active editor.
* @param context
* The application context.
* @param importedFile
* The mapping file to load merge.
* @param importFile
* The mapping file export to import.
*/
constructor(context: ApplicationStore, importedFile: MappingFileExport) {
constructor(context: ApplicationStore, importFile: MappingFileExport) {
super();
this._context = context;
this._importedFile = importedFile;
const activeEditorFile = this._context.activeEditor.file;
// throw error if imported file's source framework does not match the current file's source framework
if (activeEditorFile.sourceFramework !== this._importedFile.source_framework) {
alert("The imported file's mapping framework must be the same as the active file's mapping framework.")
throw new Error(`The imported file's mapping framework must be the same as the active file's mapping framework.`)
}
// throw error if imported file's target framework does not match the current file's target framework
if (activeEditorFile.targetFramework !== this._importedFile.target_framework){
alert("The imported file's target framework must be the same as the active file's target framework.")
throw new Error(`The imported file's target framework must be the same as the active file's target framework.`)
}
// if versions do not match, explicitly set the imported file's mapping objects to the version
if (activeEditorFile.sourceVersion !== this._importedFile.source_version) {
for (const mappingObject of this._importedFile.mapping_objects){
mappingObject.source_version = this._importedFile.source_version
}
this.editor = context.activeEditor as MappingFileEditor;
this.fileAuthority = context.fileAuthority as MappingFileAuthority;
// Validate source framework
if(this.editor.file.sourceFramework !== importFile.source_framework) {
throw new Error(
`The imported file's source framework ('${
importFile.source_framework
}') doesn't match this file's source framework ('${
this.editor.file.sourceFramework
}').`
);
}
if (activeEditorFile.targetVersion !== this._importedFile.target_version) {
for (const mappingObject of this._importedFile.mapping_objects){
mappingObject.target_version = this._importedFile.target_version
}
// Validate target framework
if(this.editor.file.targetFramework !== importFile.target_framework) {
throw new Error(
`The imported file's target framework ('${
importFile.target_framework
}') doesn't match this file's target framework ('${
this.editor.file.targetFramework
}').`
);
}
this.importFile = importFile;
}


/**
* Executes the command.
*/
public execute(): void {
// unselect any items that are currently selected
this._context.activeEditor.view.setAllItemsSelect(false);

let objIds: Set<string> = new Set();
let objects: MappingObject[] = [];

let rawFile = MappingFileView.toRaw(this._context.activeEditor.file);
let rawFileAuthority = MappingFileView.toRaw(this._context.fileAuthority)
for (const mappingObj of this._importedFile.mapping_objects){
const mappingObjExport = rawFileAuthority.initializeMappingObjectExport(
mappingObj, rawFile as MappingFile
);
objects.push(mappingObjExport)
// store ids of inserted objects
objIds.add(mappingObjExport.id);
}
rawFile.insertMappingObjectsAfter(objects);
this._context.activeEditor.view.rebuildBreakouts();

// move view to the item in imported items that has the lowest headOffset
const topItem = this._context.activeEditor.view.getItems(o => objIds.has(o.id)).next().value;
this._context.activeEditor.view.moveToViewItem(topItem.object.id, 0, true, false);

// select each inserted object
for(const objId of objIds){
this._context.activeEditor.view.getItem(objId).select(true);
const file = this.editor.file;
const view = this.editor.view;
const rawEditor = Reactivity.toRaw(this.editor);
const rawFileAuthority = Reactivity.toRaw(this.fileAuthority);
// Compile mapping items
const objs = new Map();
for(const exp of this.importFile.mapping_objects) {
const obj = rawFileAuthority.initializeMappingObjectExport(exp, rawEditor.file);
objs.set(obj.id, obj);
}
this._context.activeEditor.reindexFile([...objIds]);
// Configure view command
const cmd = EditorCommands.createSplitPhaseViewCommand(
EditorCommands.insertMappingObjects(file, [...objs.values()]),
() => [
EditorCommands.rebuildViewBreakouts(view),
EditorCommands.unselectAllMappingObjectViews(view),
EditorCommands.selectMappingObjectViewsById(view, [...objs.keys()])
]
)
// Execute insert
this.editor.execute(cmd);
// Move first item into view
const firstItem = view.getItems(o => objs.has(o.id)).next().value;
view.moveToViewItem(firstItem.object.id, 0, true, false);
}

}
Loading

0 comments on commit 8f41917

Please sign in to comment.