Skip to content

Commit

Permalink
Display GenAI analysis in the case page in the Web UI (#5301)
Browse files Browse the repository at this point in the history
* Display GenAI analysis in the case page in the Web UI

* adds GenaiAnalysisDisplay.vue component

* makes eslint happy

* Sanitize the formatted text to avoid XSS
  • Loading branch information
mvilanova authored Oct 9, 2024
1 parent f1c7435 commit e825b87
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/dispatch/static/dispatch/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ declare module '@vue/runtime-core' {
DefaultLayout: typeof import('./src/components/layouts/DefaultLayout.vue')['default']
DMenu: typeof import('./src/components/DMenu.vue')['default']
DTooltip: typeof import('./src/components/DTooltip.vue')['default']
GenaiAnalysisDisplay: typeof import('./src/components/GenaiAnalysisDisplay.vue')['default']
IconPickerInput: typeof import('./src/components/IconPickerInput.vue')['default']
InfoWidget: typeof import('./src/components/InfoWidget.vue')['default']
Loading: typeof import('./src/components/Loading.vue')['default']
Expand Down
6 changes: 6 additions & 0 deletions src/dispatch/static/dispatch/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/dispatch/static/dispatch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"d3-force": "^3.0.0",
"date-fns": "^2.30.0",
"date-fns-tz": "^1.3.8",
"dompurify": "^3.1.7",
"dotenv": "^16.3.1",
"font-awesome": "^4.7.0",
"happy-dom": "^12.10.3",
Expand Down
37 changes: 22 additions & 15 deletions src/dispatch/static/dispatch/src/case/Page.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<template>
<div>
<PageHeader
:case-id="caseDetails.id"
:case-description="caseDetails.description"
:case-genai-analysis="caseDetails.genai_analysis"
:case-id="caseDetails.id"
:case-name="caseDetails.name"
:case-visibility="caseDetails.visibility"
:case-status="caseDetails.status"
:case-title="caseDetails.title"
:case-updated-at="caseDetails.updated_at"
:case-visibility="caseDetails.visibility"
:is-drawer-open="isDrawerOpen"
:active-tab="activeTab"
@toggle-drawer="toggleDrawer"
Expand All @@ -27,6 +28,11 @@
class="pl-8 pb-6"
@update:model-value="handleDescriptionUpdate"
/>
<GenaiAnalysisDisplay
v-if="activeTab !== 'signals' && activeTab !== 'graph'"
:analysis="caseDetails.genai_analysis"
class="pl-8 pb-6"
/>
<CaseStatusSelectGroup
v-if="activeTab !== 'signals' && activeTab !== 'graph'"
v-model="caseDetails"
Expand All @@ -43,55 +49,56 @@
</template>

<script setup lang="ts">
import { debounce } from "lodash"
import { ref, watch } from "vue"
import { useStore } from "vuex"
import { useRoute } from "vue-router"
import { debounce } from "lodash"
import { useSavingState } from "@/composables/useSavingState"
import { useStore } from "vuex"
import CaseApi from "@/case/api"
import CaseAttributesDrawer from "@/case/CaseAttributesDrawer.vue"
import PageHeader from "@/case//PageHeader.vue"
import CaseStatusSelectGroup from "@/case/CaseStatusSelectGroup.vue"
import CaseTabs from "@/case/CaseTabs.vue"
import PageHeader from "@/case/PageHeader.vue"
import RichEditor from "@/components/RichEditor.vue"
import CaseStatusSelectGroup from "@/case/CaseStatusSelectGroup.vue"
import { useSavingState } from "@/composables/useSavingState"
import GenaiAnalysisDisplay from "@/components/GenaiAnalysisDisplay.vue"
const route = useRoute()
const store = useStore()
const caseDefaults = {
status: "New",
assignee: null,
case_priority: null,
case_severity: null,
case_type: null,
closed_at: null,
conversation: null,
description: "",
documents: [],
duplicates: [],
escalated_at: null,
participants: [],
events: [],
genai_analysis: null,
groups: [],
id: null,
incidents: [],
name: null,
participants: [],
project: null,
related: [],
reporter: null,
reported_at: null,
resolution_reason: "",
reporter: null,
resolution: "",
title: "",
resolution_reason: "",
signal_instances: [],
status: "New",
storage: null,
tags: [],
ticket: null,
title: "",
triage_at: null,
updated_at: null,
visibility: "",
conversation: null,
workflow_instances: null,
}
Expand Down Expand Up @@ -173,7 +180,7 @@ watch(

<style scoped>
.container {
max-width: 1920px; /* for example */
max-width: 1920px;
padding-left: 1rem;
padding-right: 1rem;
margin-left: auto;
Expand Down
9 changes: 5 additions & 4 deletions src/dispatch/static/dispatch/src/case/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ const getDefaultSelectedState = () => {
case_priority: null,
case_severity: null,
case_type: null,
dedicated_channel: true,
closed_at: null,
conversation: null,
dedicated_channel: true,
description: null,
documents: [],
duplicates: [],
escalated_at: null,
events: [],
genai_analysis: null,
groups: [],
id: null,
incidents: [],
Expand All @@ -30,10 +32,10 @@ const getDefaultSelectedState = () => {
participant: null,
project: null,
related: [],
reporter: null,
reported_at: null,
resolution_reason: null,
reporter: null,
resolution: null,
resolution_reason: null,
saving: false,
signals: [],
status: null,
Expand All @@ -44,7 +46,6 @@ const getDefaultSelectedState = () => {
triage_at: null,
updated_at: null,
visibility: null,
conversation: null,
workflow_instances: null,
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<template>
<div>
<h2>GenAI Analysis</h2>
<div v-if="analysis && Object.keys(analysis).length > 0">
<div v-for="(value, key) in analysis" :key="key" class="analysis-item">
<h3>
<b>{{ key }}</b>
</h3>
<div v-if="isObject(value)">
<!-- Render each sub-value with formatText to handle links and code formatting -->
<span
v-for="(subValue, subKey) in value"
:key="subKey"
v-html="formatText(subValue)"
></span>
</div>
<span v-else v-html="formatText(value)"></span>
</div>
</div>
<div v-else>
<p>A GenAI analysis does not exist for this case.</p>
</div>
</div>
</template>

<script setup lang="ts">
import { defineProps } from "vue"
import DOMPurify from "dompurify"
// Define the `analysis` prop
defineProps({
analysis: {
type: Object,
required: false,
default: null,
},
})
// Helper function to check if a value is an object
function isObject(value) {
return typeof value === "object" && value !== null
}
// Function to format text, converting <URL|text> format to clickable links and wrapping `code` with <code> tags
function formatText(text) {
if (typeof text !== "string") return text
// Convert <URL|text> format to clickable links
let formattedText = text.replace(/<([^|]+)\|([^>]+)>/g, '<a href="$1" target="_blank">$2</a>')
// Convert `code` format to <code>code</code> for inline code styling
formattedText = formattedText.replace(/`([^`]+)`/g, "<code>$1</code>")
// Sanitize the formatted text to avoid XSS
return DOMPurify.sanitize(formattedText)
}
</script>

<style scoped>
h2 {
margin-bottom: 1rem;
font-size: 1.5em;
}
.analysis-item h3 {
margin-top: 1em;
}
a {
color: #1a73e8;
text-decoration: underline;
}
code {
font-family: monospace;
background-color: #f5f5f5;
padding: 0.2em 0.4em;
border-radius: 4px;
}
</style>

0 comments on commit e825b87

Please sign in to comment.