Skip to content

Commit 3828360

Browse files
authored
Context edit (#252)
1 parent 7bbeb26 commit 3828360

File tree

4 files changed

+207
-97
lines changed

4 files changed

+207
-97
lines changed

src/components/DFIQ/DFIQApproaches.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<v-expansion-panels multiple flaat static>
2+
<v-expansion-panels multiple flat static>
33
<v-expansion-panel v-for="(approach, index) in question.approaches">
44
<v-expansion-panel-title class="approach-title d-flex justify-end">
55
<span class="me-4">{{ approach.name }}</span>

src/components/ObjectContext.vue

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<template>
2+
<v-card class="ma-2">
3+
<v-expansion-panels :readonly="this.context.length === 0" flat static>
4+
<v-expansion-panel>
5+
<v-expansion-panel-title class="edit-ctx-title break-title">
6+
<p class="text-subtitle-1 me-2">Context entries</p>
7+
<v-chip
8+
label
9+
color="green"
10+
v-for="source in new Set(context.map(c => c.source))"
11+
v-bind:key="source"
12+
size="small"
13+
density="comfortable"
14+
class="me-2"
15+
>
16+
{{ source }}
17+
</v-chip>
18+
19+
<v-dialog height="90%">
20+
<template v-slot:activator="{ props }">
21+
<v-btn v-bind="props" class="ms-2 edit-ctx-btn" size="small" variant="text" density="comfortable"
22+
><v-icon class="me-2">mdi-pencil</v-icon>{{ this.context.length === 0 ? "add" : "edit" }}</v-btn
23+
>
24+
</template>
25+
<template v-slot:default="{ isActive }">
26+
<v-card max-height="100%">
27+
<v-card-title class="text-h6">Edit context</v-card-title>
28+
<v-card-text>
29+
<v-textarea class="yeti-code" v-model="jsonContext" v-on:input="checkJson" rows="1" auto-grow />
30+
<v-alert type="error" v-if="jsonError !== ''">Invalid JSON: {{ jsonError }}</v-alert>
31+
</v-card-text>
32+
<v-card-actions>
33+
<v-btn color="cancel" variant="tonal" @click="isActive.value = false">Cancel</v-btn>
34+
<v-btn color="primary" variant="tonal" @click="updateContext(isActive)">Save</v-btn>
35+
</v-card-actions>
36+
</v-card>
37+
</template>
38+
</v-dialog>
39+
</v-expansion-panel-title>
40+
<v-expansion-panel-text>
41+
<v-treeview
42+
:items="ContextTreeView()"
43+
item-value="id"
44+
activatable
45+
open-on-click
46+
slim
47+
density="compact"
48+
max-height="300"
49+
width="100%"
50+
v-if="showCtxDetails"
51+
>
52+
</v-treeview>
53+
</v-expansion-panel-text>
54+
</v-expansion-panel>
55+
</v-expansion-panels>
56+
</v-card>
57+
</template>
58+
59+
<script>
60+
import axios from "axios";
61+
import { VTreeview } from "vuetify/labs/VTreeview";
62+
63+
export default {
64+
components: { VTreeview },
65+
props: {
66+
context: {
67+
type: Object,
68+
required: true
69+
},
70+
updateEndpoint: {
71+
type: String,
72+
required: true
73+
}
74+
},
75+
data() {
76+
return {
77+
jsonContext: JSON.stringify(this.context, null, 2),
78+
jsonError: "",
79+
showCtxDetails: true
80+
};
81+
},
82+
// watch: {
83+
// context: {
84+
// handler(newContext) {
85+
// this.jsonContext = JSON.stringify(newContext, null, 2);
86+
// },
87+
// deep: true
88+
// }
89+
// },
90+
methods: {
91+
checkJson() {
92+
try {
93+
JSON.parse(this.jsonContext);
94+
this.jsonError = "";
95+
} catch (e) {
96+
this.jsonError = e.message;
97+
}
98+
return this.jsonError;
99+
},
100+
updateContext(isActive) {
101+
let contextPayload;
102+
try {
103+
contextPayload = JSON.parse(this.jsonContext);
104+
} catch (e) {
105+
return;
106+
}
107+
axios
108+
.put(`/api/v2/${this.updateEndpoint}/context`, { context: contextPayload })
109+
.then(response => {
110+
this.$emit("update:context", response.data.context);
111+
isActive.value = false;
112+
})
113+
.catch(error => {
114+
console.log(error);
115+
});
116+
},
117+
ContextTreeView() {
118+
var items = [];
119+
var id = 0;
120+
121+
function treeify(root, data) {
122+
if (Array.isArray(data)) {
123+
var count = 0;
124+
for (const item of data) {
125+
var element = { id: id++, title: count.toString(), children: [] };
126+
root.push(element);
127+
count++;
128+
treeify(element.children, item);
129+
}
130+
} else if (typeof data === "object") {
131+
for (const [key, value] of Object.entries(data)) {
132+
if (key === "source") {
133+
continue;
134+
}
135+
if (value === null) {
136+
var title = key + ": N/A";
137+
var element = { id: id++, title: title };
138+
root.push(element);
139+
} else if (Array.isArray(value) || typeof value === "object") {
140+
element = { id: id++, title: key, children: [] };
141+
root.push(element);
142+
treeify(element.children, value);
143+
} else {
144+
var title = key + ": " + value;
145+
var element = { id: id++, title: title };
146+
root.push(element);
147+
}
148+
}
149+
}
150+
}
151+
for (const ctx of this.context) {
152+
var element = { id: id++, title: ctx.source, children: [], color: "warning" };
153+
items.push(element);
154+
treeify(element.children, ctx);
155+
}
156+
return items;
157+
}
158+
}
159+
};
160+
</script>
161+
162+
<style>
163+
.yeti-code-container {
164+
max-height: 100%;
165+
overflow: auto;
166+
}
167+
168+
.yeti-code textarea,
169+
.yeti-code input {
170+
font-family: monospace;
171+
}
172+
173+
.edit-ctx-title .edit-ctx-btn {
174+
display: none;
175+
}
176+
177+
.edit-ctx-title:hover .edit-ctx-btn {
178+
display: inline;
179+
}
180+
</style>

src/views/ObjectDetails.vue

+9-12
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,12 @@
2828
</v-card-text></v-card
2929
>
3030

31-
<v-card v-for="(context, index) in object?.context" class="ma-2" variant="flat">
32-
<v-card-title>{{ context.source }}</v-card-title>
33-
<v-table>
34-
<tbody>
35-
<tr v-for="key in Object.keys(context).filter(k => k !== 'source')" v-bind:key="key">
36-
<th>{{ key }}</th>
37-
<td><yeti-markdown :text="context[key]" /></td>
38-
</tr>
39-
</tbody>
40-
</v-table>
41-
</v-card>
31+
<object-context
32+
v-if="object"
33+
:context="object.context"
34+
@update:context="ctx => (object.context = ctx)"
35+
:update-endpoint="`${typeToEndpointMapping[objectType]}/${object.id}`"
36+
/>
4237
</v-col>
4338
<v-col cols="4">
4439
<v-card class="ma-2" variant="flat">
@@ -300,6 +295,7 @@ import EditDFIQObject from "@/components/DFIQ/EditDFIQObject.vue";
300295
import LinkObject from "@/components/LinkObject.vue";
301296
import LinkObservables from "@/components/LinkObservables.vue";
302297
import YetiMarkdown from "@/components/YetiMarkdown.vue";
298+
import ObjectContext from "@/components/ObjectContext.vue";
303299
304300
import { ENTITY_TYPES } from "@/definitions/entityDefinitions.js";
305301
import { OBSERVABLE_TYPES } from "@/definitions/observableDefinitions.js";
@@ -336,7 +332,8 @@ export default {
336332
DFIQTree,
337333
GraphObjects,
338334
Timeline,
339-
ACLEdit
335+
ACLEdit,
336+
ObjectContext
340337
},
341338
data() {
342339
return {

src/views/ObservableDetails.vue

+17-84
Original file line numberDiff line numberDiff line change
@@ -35,43 +35,20 @@
3535
</v-expansion-panel>
3636
</v-expansion-panels>
3737
</v-sheet>
38-
<v-sheet class="ma-2">
39-
<v-table density="compact">
40-
<tbody>
41-
<tr>
42-
<th>Context sources</th>
43-
<td>
44-
<v-chip
45-
label
46-
color="green"
47-
v-for="source in new Set(observable?.context.map(c => c.source))"
48-
v-bind:key="source"
49-
size="small"
50-
>
51-
{{ source }}
52-
</v-chip>
53-
</td>
54-
</tr>
55-
<tr v-for="field in getObservableDetailFields">
56-
<th>{{ field.label }}</th>
57-
<td>{{ observable[field.field] }}</td>
58-
</tr>
59-
</tbody>
60-
</v-table>
61-
<v-card>
62-
<v-treeview
63-
:items="ContextTreeView()"
64-
item-value="id"
65-
activatable
66-
open-on-click
67-
open-all
68-
density="compact"
69-
max-height="300"
70-
width="100%"
71-
>
72-
</v-treeview>
73-
</v-card>
74-
</v-sheet>
38+
<object-context
39+
v-if="observable"
40+
:context="observable.context"
41+
@update:context="ctx => (observable.context = ctx)"
42+
:update-endpoint="`observables/${observable.id}`"
43+
/>
44+
<v-table>
45+
<tbody>
46+
<tr v-for="field in getObservableDetailFields">
47+
<th>{{ field.label }}</th>
48+
<td>{{ observable[field.field] }}</td>
49+
</tr>
50+
</tbody>
51+
</v-table>
7552
</v-col>
7653
<v-col cols="4">
7754
<v-card class="ma-2" variant="flat" v-if="observable">
@@ -284,6 +261,7 @@ import TaskList from "@/components/TaskList.vue";
284261
import RelatedObjects from "@/components/RelatedObjects.vue";
285262
import EditObject from "@/components/EditObject.vue";
286263
import ACLEdit from "@/components/ACLEdit.vue";
264+
import ObjectContext from "@/components/ObjectContext.vue";
287265
import DirectNeighbors from "@/components/DirectNeighbors.vue";
288266
import GraphObjects from "@/components/GraphObjects.vue";
289267
@@ -294,7 +272,6 @@ import { ENTITY_TYPES } from "@/definitions/entityDefinitions.js";
294272
import { INDICATOR_TYPES } from "@/definitions/indicatorDefinitions.js";
295273
import { OBSERVABLE_TYPES } from "@/definitions/observableDefinitions.js";
296274
import moment from "moment";
297-
import { VTreeview } from "vuetify/labs/VTreeview";
298275
299276
import Timeline from "@/components/Timeline.vue";
300277
@@ -318,7 +295,8 @@ export default {
318295
LinkObservables,
319296
GraphObjects,
320297
Timeline,
321-
ACLEdit
298+
ACLEdit,
299+
ObjectContext
322300
},
323301
data() {
324302
return {
@@ -388,51 +366,6 @@ export default {
388366
toggleFullscreen(fullscreen: boolean) {
389367
this.fullScreenEdit = !this.fullScreenEdit;
390368
this.editWidth = fullscreen ? "100%" : "75%";
391-
},
392-
393-
ContextTreeView() {
394-
var items = [];
395-
var id = 0;
396-
var id = 0;
397-
if (this.observable === null) {
398-
return items;
399-
}
400-
function treeify(root, data) {
401-
if (Array.isArray(data)) {
402-
var count = 0;
403-
for (const item of data) {
404-
var element = { id: id++, title: count.toString(), children: [] };
405-
root.push(element);
406-
count++;
407-
treeify(element.children, item);
408-
}
409-
} else if (typeof data === "object") {
410-
for (const [key, value] of Object.entries(data)) {
411-
if (key === "source") {
412-
continue;
413-
}
414-
if (value === null) {
415-
var title = key + ": N/A";
416-
var element = { id: id++, title: title };
417-
root.push(element);
418-
} else if (Array.isArray(value) || typeof value === "object") {
419-
element = { id: id++, title: key, children: [] };
420-
root.push(element);
421-
treeify(element.children, value);
422-
} else {
423-
var title = key + ": " + value;
424-
var element = { id: id++, title: title };
425-
root.push(element);
426-
}
427-
}
428-
}
429-
}
430-
for (const ctx of this.observable.context) {
431-
var element = { id: id++, title: ctx.source, children: [], color: "warning" };
432-
items.push(element);
433-
treeify(element.children, ctx);
434-
}
435-
return items;
436369
}
437370
},
438371

0 commit comments

Comments
 (0)