Skip to content

Commit

Permalink
Merge branch 'master' into enhancement/escalate-fire-icon
Browse files Browse the repository at this point in the history
  • Loading branch information
wssheldon authored Nov 30, 2023
2 parents dc10133 + d0fa083 commit 6490fb7
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/dispatch/case/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ class CaseRead(CaseBase):
tags: Optional[List[TagRead]] = []
ticket: Optional[TicketRead] = None
triage_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
workflow_instances: Optional[List[WorkflowInstanceRead]] = []


Expand Down
7 changes: 7 additions & 0 deletions src/dispatch/static/dispatch/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,28 @@ declare module '@vue/runtime-core' {
DateTimePickerMenu: typeof import('./src/components/DateTimePickerMenu.vue')['default']
DateWindowInput: typeof import('./src/components/DateWindowInput.vue')['default']
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']
InfoWidget: typeof import('./src/components/InfoWidget.vue')['default']
Loading: typeof import('./src/components/Loading.vue')['default']
LockButton: typeof import('./src/components/LockButton.vue')['default']
MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default']
NotificationSnackbarsWrapper: typeof import('./src/components/NotificationSnackbarsWrapper.vue')['default']
PageHeader: typeof import('./src/components/PageHeader.vue')['default']
ParticipantAutoComplete: typeof import('./src/components/ParticipantAutoComplete.vue')['default']
ParticipantSelect: typeof import('./src/components/ParticipantSelect.vue')['default']
ProjectAutoComplete: typeof import('./src/components/ProjectAutoComplete.vue')['default']
Refresh: typeof import('./src/components/Refresh.vue')['default']
RichEditor: typeof import('./src/components/RichEditor.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SavingState: typeof import('./src/components/SavingState.vue')['default']
SearchPopover: typeof import('./src/components/SearchPopover.vue')['default']
SettingsBreadcrumbs: typeof import('./src/components/SettingsBreadcrumbs.vue')['default']
ShepherdStep: typeof import('./src/components/ShepherdStep.vue')['default']
ShpherdStep: typeof import('./src/components/ShpherdStep.vue')['default']
StatWidget: typeof import('./src/components/StatWidget.vue')['default']
SubjectLastUpdated: typeof import('./src/components/SubjectLastUpdated.vue')['default']
VAlert: typeof import('vuetify/lib')['VAlert']
VApp: typeof import('vuetify/lib')['VApp']
VAppBar: typeof import('vuetify/lib')['VAppBar']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CaseSeveritySearchPopover from "@/case/severity/CaseSeveritySearchPopover
import CaseTypeSearchPopover from "@/case/type/CaseTypeSearchPopover.vue"
import ParticipantSearchPopover from "@/participant/ParticipantSearchPopover.vue"
import ProjectSearchPopover from "@/project/ProjectSearchPopover.vue"
import { useSavingState } from "@/composables/useSavingState"
// Define the props
const props = defineProps({
Expand All @@ -25,7 +26,7 @@ const props = defineProps({
// Define the emits
const emit = defineEmits(["update:modelValue", "update:open"])
const drawerVisible = ref(props.open)
// Create a local state for modelValue
const { setSaving } = useSavingState()
const modelValue = ref({ ...props.modelValue })
watch(
Expand All @@ -52,7 +53,9 @@ const handleResolutionUpdate = (newResolution) => {
const saveCaseDetails = async () => {
try {
setSaving(true)
await CaseApi.update(modelValue.value.id, modelValue.value)
setSaving(false)
} catch (e) {
console.error("Failed to save case details", e)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import type { Ref } from "vue"
import CaseApi from "@/case/api"
import SearchPopover from "@/components/SearchPopover.vue"
import { useSavingState } from "@/composables/useSavingState"
import { useStore } from "vuex"
defineProps<{ caseResolution: string }>()
const store = useStore()
const { setSaving } = useSavingState()
const caseResolutions: Ref<string[]> = ref([
"False Positive",
"User Acknowledged",
Expand All @@ -22,7 +23,9 @@ const selectCaseResolution = async (caseResolutionName: string) => {
const caseDetails = store.state.case_management.selected
caseDetails.resolution_reason = caseResolutionName
setSaving(true)
await CaseApi.update(caseDetails.id, caseDetails)
setSaving(false)
}
</script>

Expand Down
6 changes: 6 additions & 0 deletions src/dispatch/static/dispatch/src/case/Page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
:case-name="caseDetails.name"
:case-visibility="caseDetails.visibility"
:case-status="caseDetails.status"
:case-updated-at="caseDetails.updated_at"
:is-drawer-open="isDrawerOpen"
@toggle-drawer="toggleDrawer"
/>
Expand Down Expand Up @@ -39,6 +40,7 @@ import PageHeader from "@/case//PageHeader.vue"
import CaseTabs from "@/case/CaseTabs.vue"
import RichEditor from "@/components/RichEditor.vue"
import CaseStatusSelectGroup from "@/case/CaseStatusSelectGroup.vue"
import { useSavingState } from "@/composables/useSavingState"
const route = useRoute()
const store = useStore()
Expand Down Expand Up @@ -72,6 +74,7 @@ const caseDefaults = {
tags: [],
ticket: null,
triage_at: null,
updated_at: null,
visibility: "",
conversation: null,
workflow_instances: null,
Expand All @@ -80,6 +83,7 @@ const caseDefaults = {
const caseDetails = ref(caseDefaults)
const loading = ref(false)
const isDrawerOpen = ref(true)
const { setSaving } = useSavingState()
const toggleDrawer = () => {
isDrawerOpen.value = !isDrawerOpen.value
Expand Down Expand Up @@ -115,7 +119,9 @@ const handleDescriptionUpdate = (newDescription) => {
const saveCaseDetails = async () => {
try {
setSaving(true)
await CaseApi.update(caseDetails.value.id, caseDetails.value)
setSaving(false)
} catch (e) {
console.error("Failed to save case details", e)
}
Expand Down
7 changes: 7 additions & 0 deletions src/dispatch/static/dispatch/src/case/PageHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<v-icon size="x-small" icon="mdi-chevron-right" />
</template>
</v-breadcrumbs>
<SavingState :updatedAt="caseUpdatedAt" />

<template #append>
<DTooltip text="View case participants" :hotkeys="['⌘', '⇧', 'P']">
<template #activator="{ tooltip }">
Expand Down Expand Up @@ -36,6 +38,7 @@ import LockButton from "@/components/LockButton.vue"
import EscalateButton from "@/case/EscalateButton.vue"
import DTooltip from "@/components/DTooltip.vue"
import ParticipantAvatarGroup from "@/participant/ParticipantAvatarGroup.vue"
import SavingState from "@/components/SavingState.vue"
import CaseApi from "@/case/api"
const route = useRoute()
Expand All @@ -53,6 +56,10 @@ const props = defineProps({
type: String,
required: true,
},
caseUpdatedAt: {
type: String,
required: true,
},
isDrawerOpen: {
type: Boolean,
default: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { onMounted, ref } from "vue"
import CasePriorityApi from "@/case/priority/api"
import CaseApi from "@/case/api"
import SearchPopover from "@/components/SearchPopover.vue"
import { useSavingState } from "@/composables/useSavingState"
import type { Ref } from "vue"
import { useStore } from "vuex"
Expand All @@ -13,7 +14,7 @@ type CasePriority = {
defineProps<{ casePriority: string }>()
const store = useStore()
const { setSaving } = useSavingState()
const casePriorities: Ref<CasePriority[]> = ref([])
onMounted(async () => {
Expand All @@ -39,7 +40,9 @@ const selectCasePriority = async (casePriorityName: string) => {
const caseDetails = store.state.case_management.selected
caseDetails.case_priority = caseType
setSaving(true)
await CaseApi.update(caseDetails.id, caseDetails)
setSaving(false)
}
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { onMounted, ref } from "vue"
import CaseSeverityApi from "@/case/severity/api"
import CaseApi from "@/case/api"
import SearchPopover from "@/components/SearchPopover.vue"
import { useSavingState } from "@/composables/useSavingState"
import type { Ref } from "vue"
import { useStore } from "vuex"
Expand All @@ -13,7 +14,7 @@ type CaseSeverity = {
defineProps<{ caseSeverity: string }>()
const store = useStore()
const { setSaving } = useSavingState()
const caseSeveritys: Ref<CaseSeverity[]> = ref([])
onMounted(async () => {
Expand All @@ -39,7 +40,9 @@ const selectCaseSeverity = async (caseSeverityName: string) => {
const caseDetails = store.state.case_management.selected
caseDetails.case_severity = caseSeverity
setSaving(true)
await CaseApi.update(caseDetails.id, caseDetails)
setSaving(false)
}
</script>

Expand Down
6 changes: 6 additions & 0 deletions src/dispatch/static/dispatch/src/case/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ const getDefaultSelectedState = () => {
reported_at: null,
resolution_reason: null,
resolution: null,
saving: false,
signals: [],
status: null,
storage: null,
tags: [],
ticket: null,
title: null,
triage_at: null,
updated_at: null,
visibility: null,
conversation: null,
workflow_instances: null,
Expand Down Expand Up @@ -93,6 +95,7 @@ const state = {
sortBy: ["reported_at"],
descending: [true],
},
saving: false,
loading: false,
bulkEditLoading: false,
},
Expand Down Expand Up @@ -447,6 +450,9 @@ const mutations = {
SET_SELECTED_LOADING(state, value) {
state.selected.loading = value
},
SET_SELECTED_SAVING(state, value) {
state.selected.saving = value
},
}

export default {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { onMounted, ref } from "vue"
import CaseTypeApi from "@/case/type/api"
import CaseApi from "@/case/api"
import SearchPopover from "@/components/SearchPopover.vue"
import { useSavingState } from "@/composables/useSavingState"
import type { Ref } from "vue"
import { useStore } from "vuex"
Expand All @@ -13,7 +14,7 @@ type CaseType = {
defineProps<{ caseType: string }>()
const store = useStore()
const { setSaving } = useSavingState()
const caseTypes: Ref<CaseType[]> = ref([])
onMounted(async () => {
Expand All @@ -39,7 +40,9 @@ const selectCaseType = async (caseTypeName: string) => {
const caseDetails = store.state.case_management.selected
caseDetails.case_type = caseType
setSaving(true)
await CaseApi.update(caseDetails.id, caseDetails)
setSaving(false)
}
</script>

Expand Down
51 changes: 51 additions & 0 deletions src/dispatch/static/dispatch/src/components/SavingState.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<div>
<v-progress-circular
v-if="saving"
indeterminate
size="14"
color="grey-lighten-1"
class="pl-6"
/>
<v-icon size="x-small" class="pl-4" v-else>mdi-check</v-icon>
<span class="pl-4 dispatch-text-subtitle">updated {{ formattedUpdatedAt }}</span>
</div>
</template>

<script setup lang="ts">
import { ref, watch, watchEffect } from "vue"
import { formatDistanceToNow, parseISO } from "date-fns"
import { useSavingState } from "@/composables/useSavingState"
const { saving } = useSavingState()
let formattedUpdatedAt = ref("")
let updatedAtRef = ref("")
watchEffect(() => {
if (updatedAtRef.value) {
formattedUpdatedAt.value = formatDistanceToNow(parseISO(updatedAtRef.value)) + " ago"
}
})
watch(saving, (newVal, oldVal) => {
if (oldVal === true && newVal === false) {
updatedAtRef.value = new Date().toISOString()
}
})
const props = defineProps({
updatedAt: {
type: String,
required: true,
},
})
watch(
() => props.updatedAt,
(newVal) => {
if (newVal) {
updatedAtRef.value = newVal
}
}
)
</script>
25 changes: 25 additions & 0 deletions src/dispatch/static/dispatch/src/composables/useSavingState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { computed, ComputedRef } from "vue"
import { useStore } from "vuex"
import { Store } from "vuex"
import { CaseState } from "@/store/case"

interface UseSavingStateReturns {
saving: ComputedRef<boolean>
// eslint-disable-next-line no-unused-vars
setSaving: (value: boolean) => void
}

export function useSavingState(): UseSavingStateReturns {
const store = useStore<Store<{ case: CaseState }>>()

const saving = computed(() => store.state.case_management.selected.saving)

const setSaving = (value: boolean) => {
store.commit("case_management/SET_SELECTED_SAVING", value)
}

return {
saving,
setSaving,
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, watch, computed } from "vue"
import { useSavingState } from "@/composables/useSavingState"
import IndividualApi from "@/individual/api"
import Hotkey from "@/atomics/Hotkey.vue"
import { useHotKey } from "@/composables/useHotkey"
Expand All @@ -24,6 +25,7 @@ const props = withDefaults(
)
const store = useStore()
const { setSaving } = useSavingState()
const menu: Ref<boolean> = ref(false)
const participants: Ref<string[]> = ref([])
const selectedParticipant: Ref<string> = ref("")
Expand Down Expand Up @@ -83,8 +85,13 @@ watch(selectedParticipant, async (newValue: string) => {
caseDetails.reporter.individual = individual
}
// Call the CaseApi.update method to update the case details
await CaseApi.update(caseDetails.id, caseDetails)
setSaving(true)
try {
await CaseApi.update(caseDetails.id, caseDetails)
} catch (error) {
console.error("Error updating case:", error)
}
setSaving(false)
}
})
Expand Down
Loading

0 comments on commit 6490fb7

Please sign in to comment.