Skip to content

Commit

Permalink
Merge pull request #37 from center-for-threat-informed-defense/TIE-78…
Browse files Browse the repository at this point in the history
…_install_analytics

TIE-78: Install Google Analytics
  • Loading branch information
mehaase authored Sep 5, 2024
2 parents 7bc3cb8 + 46f235c commit 2ab6f93
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 12 deletions.
8 changes: 8 additions & 0 deletions src/tie-web-interface/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
<link href="https://fonts.googleapis.com/css2?family=Oswald:[email protected]&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Libre+Franklin:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NLXWXCSGXF"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());

gtag('config', 'G-NLXWXCSGXF', { 'anonymize_ip': true });
</script>
</head>

<body>
Expand Down
125 changes: 125 additions & 0 deletions src/tie-web-interface/src/assets/scripts/Application/EventRecorder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { SetViewFilter } from "../PredictionsView/Commands/SetViewFilter";
import { SetViewLimit } from "../PredictionsView/Commands/SetViewLimit";
import { SetViewOption } from "../PredictionsView/Commands/SetViewOption";
import type { EventStorage } from "./EventStorage";
import type { ControlCommand } from "../PredictionsView";

export class EventRecorder {

/**
* The recorder's event store.
*/
private _storage: EventStorage;


/**
* Creates a new {@link EventRecorder}.
* @param storage
* The recorder's event store.
*/
constructor(storage: EventStorage) {
this._storage = storage;
}


/**
* Records an "add techniques" event.
* @param method
* The method used to add the techniques.
* @param techniques
* The techniques added.
*/
public addTechniques(method: string, techniques: string[]) {
// Sanitize techniques
const sanitizedTechniques = techniques
.filter(id => id.match(/^T[0-9]{4}(?:.[0-9]{3})?$/gi))
.map(id => id.toLocaleUpperCase());
// Record
this._storage.record(
"add_techniques",
{
"method": method,
"techniques": sanitizedTechniques,
"total_techniques": sanitizedTechniques.length
}
)
}

/**
* Records a "technique prediction" event.
* @param techniqueBasis
* The observed techniques provided for the prediction.
* @param backend
* The prediction backend.
* @param time
* The prediction time (in ms).
*/
public makePrediction(techniques: string[], backend: string, time: number,) {
this._storage.record(
"make_prediction",
{
"observed_techniques": techniques,
"total_observed_techniques": techniques.length,
"prediction_backend": backend,
"prediction_time": time
}
);
}

/**
* Records an "apply view control" event.
* @param cmd
* The applied control command.
*/
public applyViewControl(cmd: ControlCommand) {
// If view filter...
if (cmd instanceof SetViewFilter) {
this._storage.record(
"apply_filter_control",
{
"control": cmd.control.name,
"filter": cmd.filter ?? "all_filters",
"value": cmd.value
}
);
return;
}
// If view option...
if (cmd instanceof SetViewOption) {
this._storage.record(
"apply_organization_control",
{
"control": cmd.control.name,
"value": cmd.value
}
);
return;
}
// If view limit...
if (cmd instanceof SetViewLimit) {
this._storage.record(
"apply_view_limit_control",
{
"control": cmd.control.name,
"value": cmd.value
}
)
return;
}
}

/**
* Records a "download file" event.
* @param fileType
* The file's type.
*/
public downloadArtifact(fileType: string) {
this._storage.record(
"download_file",
{
"file_type": fileType
}
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { RecordParameter } from "./RecordParameter";

export interface EventStorage {

/**
* Records an event to the event store.
* @param name
* The event's name.
* @param parameters
* The event's parameters.
*/
record(name: string, parameters: { [key: string]: RecordParameter; }): void;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { EventStorage } from "./EventStorage";
import type { RecordParameter } from "./RecordParameter";

declare const gtag: (
command: "event",
event_name: string,
parameters: { [key: string]: RecordParameter; }
) => void

export class GoogleEventStorage implements EventStorage {

/**
* Creates a new {@link GoogleEventStorage}.
*/
constructor() { }

/**
* Records an event to the event store.
* @remarks
* Please ensure the event `name` follows Google Analytic's naming convention of
* `[verb]_[noun]` where the verb is in the simple present tense. For example:
* - `join_group`
* - `view_item`
* - `select_item`
* - `spend_virtual_currency`
* @param name
* The event's name.
* @param parameters
* The event's parameters.
*/
record(name: string, parameters: { [key: string]: RecordParameter; }): void {
// Validate record
if (!name.match(/^[a-z][a-z_]*$/g)) {
const error = `Event name '${name}' does not follow typical convention.`;
throw new Error(error);
}
// Record event
gtag('event', name, parameters);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type RecordParameter = string | number | boolean | string[] | number[] | boolean[];
4 changes: 4 additions & 0 deletions src/tie-web-interface/src/assets/scripts/Application/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export * from "./AppConfiguration";
export * from "./EventRecorder";
export * from "./EventStorage";
export * from "./GoogleEventStorage";
export * from "./RecordParameter";
Original file line number Diff line number Diff line change
Expand Up @@ -676,3 +676,12 @@ input {
border-style: solid;
border-width: 1px;
}

@mixin selection-area {
& {
transition: background .15s;
}
&:hover {
background: var(--accent-2-border);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ svg:not(.collapsed) {
}
.technique-header {
@include scale.selection-area;
display: flex;
align-items: stretch;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@

<script lang="ts">
// Dependencies
import { Browser } from "@/assets/scripts/Utilities";
import { defineComponent, type PropType } from "vue";
import type { PredictionsView } from "@/assets/scripts/PredictionsView";
import type { ControlCommand } from "@/assets/scripts/PredictionsView/Commands";
Expand All @@ -75,7 +74,7 @@ export default defineComponent({
data: () => ({
active: false,
}),
emits: ["execute"],
emits: ["execute", "download"],
methods: {
/**
Expand All @@ -92,15 +91,15 @@ export default defineComponent({
*/
downloadViewAsCsv() {
const contents = this.view.exportViewToCsv();
Browser.downloadFile("predictions", contents, "csv");
this.$emit("download", "csv", contents);
},
/**
* Downloads the current view as an Attack Navigator Layer.
*/
downloadViewAsNavigatorLayer() {
const contents = this.view.exportViewToNavigatorLayer(false);
Browser.downloadFile("predictions_navigator_layer", contents, "json");
this.$emit("download", "navigator_layer", contents);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</div>
<div class="options-selector">
<OptionSelector class="options-field" placeholder="Add Technique" :options="allTechniqueOptions"
@select="addObservedTechnique" />
@select="addObservedTechniqueFromField" />
<button @click="addObservedTechniqueFromCsv()">
<UploadArrow class="icon" /><span>.CSV</span>
</button>
Expand All @@ -19,7 +19,7 @@
included in the training. Its inclusion here will not affect the predictions.
</template>
<template #default>
<div class="delete-icon" @click="deleteObservedTechnique(observed.id)">
<div class="action-icon" @click="deleteObservedTechnique(observed.id)">
<DeleteIcon />
</div>
</template>
Expand All @@ -32,15 +32,15 @@
<h3>Predicted Techniques</h3>
<small>{{ predictionMetadata }}</small>
</div>
<TechniquesViewController class="view-controller" :view="viewer" @execute="execute" />
<TechniquesViewController class="view-controller" :view="viewer" @execute="execute" @download="download" />
<div class="instructions" v-if="!predicted">
To generate a set of predictions, add one or more observed techniques.
</div>
<div class="techniques">
<template v-for="[key, item] of view.items" :key="key">
<component class="summary" :is="getSummaryType(item)" :item="item">
<template v-slot="{ technique }">
<div class="delete-icon" @click="addObservedTechnique(technique.id)">
<div class="action-icon" @click="addObservedTechniqueFromPivot(technique.id)">
<AddIcon />
</div>
</template>
Expand Down Expand Up @@ -167,13 +167,36 @@ export default defineComponent({
async addObservedTechnique(id: string) {
id = id.toLocaleUpperCase();
if (this.allTechniqueOptions.has(id)) {
// Add techniques
this.observed.add(id);
// Update predictions
await this.updatePredictions();
}
},
/**
* Pivots a technique to the observed set.
* @param id
* The id of the technique to add.
*/
async addObservedTechniqueFromPivot(id: string) {
// Add Technique
this.addObservedTechnique(id);
this.engine.recorder.addTechniques("technique_pivot", [id]);
// Update predictions
await this.updatePredictions();
},
/**
* Adds an observed technique to the set from the technique field.
* @param id
* The id of the technique to add.
*/
async addObservedTechniqueFromField(id: string) {
// Add Technique
this.addObservedTechnique(id);
this.engine.recorder.addTechniques("technique_field", [id]);
// Update predictions
await this.updatePredictions();
},
/**
* Adds observed techniques to the set from a CSV file.
*/
Expand All @@ -189,6 +212,7 @@ export default defineComponent({
for (let obj of objects) {
this.addObservedTechnique(obj.id);
}
this.engine.recorder.addTechniques("import_csv", objects.map(o => o.id));
// Update predictions
await this.updatePredictions();
},
Expand All @@ -214,6 +238,11 @@ export default defineComponent({
const b = this.trainedTechniques;
const techniques = new Set(a.filter(t => b.has(t)));
this.predicted = await this.engine.predictNewReport(techniques);
this.engine.recorder.makePrediction(
[...techniques],
this.predicted.metadata.humanReadableBackend,
this.predicted.metadata.time
);
} else {
this.predicted = null;
}
Expand All @@ -227,6 +256,29 @@ export default defineComponent({
*/
execute(cmd: ControlCommand) {
cmd.execute();
this.engine.recorder.applyViewControl(cmd);
},
/**
* Downloads a file to the device.
* @param type
* The file's type.
* @param contents
* The file's contents.
*/
download(type: string, contents: string) {
switch (type) {
case "csv":
Browser.downloadFile("predictions", contents, "csv");
this.engine.recorder.downloadArtifact(type);
break;
case "navigator_layer":
Browser.downloadFile("predictions_navigator_layer", contents, "json");
this.engine.recorder.downloadArtifact(type);
break;
default:
console.warn(`Cannot download unknown file type: '${type}'.`)
}
}
},
Expand Down Expand Up @@ -307,7 +359,7 @@ export default defineComponent({
margin-right: scale.size("s");
}
.delete-icon {
.action-icon {
@include color.icon;
display: flex;
align-items: center;
Expand Down
Loading

0 comments on commit 2ab6f93

Please sign in to comment.