From d9a736b6c95f7b7723f8af72b87f44e5baaec187 Mon Sep 17 00:00:00 2001
From: Will Sheldon <114631109+wssheldon@users.noreply.github.com>
Date: Mon, 4 Dec 2023 09:37:42 -0800
Subject: [PATCH 1/6] Get all items for Pri, Sev, Type, and Project in
`SearchPopover` component and add tests (#4079)
* Add tests for SearchPopover
* Get all items for Pri, Sev, Type, and Project
* Remove duplicate menu test
* Move itemsPerPage option to onMounted API calls
---
.../priority/CasePrioritySearchPopover.vue | 3 +-
.../severity/CaseSeveritySearchPopover.vue | 3 +-
.../src/case/type/CaseTypeSearchPopover.vue | 3 +-
.../src/project/ProjectSearchPopover.vue | 3 +-
.../dispatch/src/tests/SearchPopover.spec.js | 213 ++++++++++++++++++
5 files changed, 221 insertions(+), 4 deletions(-)
create mode 100644 src/dispatch/static/dispatch/src/tests/SearchPopover.spec.js
diff --git a/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue b/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue
index e6f40770347d..0c72ad93bba2 100644
--- a/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue
+++ b/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue
@@ -19,7 +19,8 @@ const casePriorities: Ref = ref([])
onMounted(async () => {
try {
- const response = await CasePriorityApi.getAll()
+ const options = { itemsPerPage: -1 }
+ const response = await CasePriorityApi.getAll(options)
casePriorities.value = response.data.items.map((item: any) => item.name)
} catch (error) {
console.error("Error fetching priorities:", error)
diff --git a/src/dispatch/static/dispatch/src/case/severity/CaseSeveritySearchPopover.vue b/src/dispatch/static/dispatch/src/case/severity/CaseSeveritySearchPopover.vue
index fad86bd0abd3..c5d8ef22589d 100644
--- a/src/dispatch/static/dispatch/src/case/severity/CaseSeveritySearchPopover.vue
+++ b/src/dispatch/static/dispatch/src/case/severity/CaseSeveritySearchPopover.vue
@@ -19,7 +19,8 @@ const caseSeveritys: Ref = ref([])
onMounted(async () => {
try {
- const response = await CaseSeverityApi.getAll()
+ const options = { itemsPerPage: -1 }
+ const response = await CaseSeverityApi.getAll(options)
caseSeveritys.value = response.data.items.map((item: any) => item.name)
} catch (error) {
console.error("Error fetching case severities:", error)
diff --git a/src/dispatch/static/dispatch/src/case/type/CaseTypeSearchPopover.vue b/src/dispatch/static/dispatch/src/case/type/CaseTypeSearchPopover.vue
index 8bc5652d823a..a7f47a39cc15 100644
--- a/src/dispatch/static/dispatch/src/case/type/CaseTypeSearchPopover.vue
+++ b/src/dispatch/static/dispatch/src/case/type/CaseTypeSearchPopover.vue
@@ -19,7 +19,8 @@ const caseTypes: Ref = ref([])
onMounted(async () => {
try {
- const response = await CaseTypeApi.getAll()
+ const options = { itemsPerPage: -1 }
+ const response = await CaseTypeApi.getAll(options)
caseTypes.value = response.data.items.map((item: any) => item.name)
} catch (error) {
console.error("Error fetching case types:", error)
diff --git a/src/dispatch/static/dispatch/src/project/ProjectSearchPopover.vue b/src/dispatch/static/dispatch/src/project/ProjectSearchPopover.vue
index 86f178a6eeb2..39c3633302d2 100644
--- a/src/dispatch/static/dispatch/src/project/ProjectSearchPopover.vue
+++ b/src/dispatch/static/dispatch/src/project/ProjectSearchPopover.vue
@@ -20,7 +20,8 @@ const projects: Ref = ref([])
onMounted(async () => {
try {
- const response = await ProjectApi.getAll()
+ const options = { itemsPerPage: -1 }
+ const response = await ProjectApi.getAll(options)
projects.value = response.data.items.map((item: any) => item.name)
} catch (error) {
console.error("Error fetching projects:", error)
diff --git a/src/dispatch/static/dispatch/src/tests/SearchPopover.spec.js b/src/dispatch/static/dispatch/src/tests/SearchPopover.spec.js
new file mode 100644
index 000000000000..10298707ef96
--- /dev/null
+++ b/src/dispatch/static/dispatch/src/tests/SearchPopover.spec.js
@@ -0,0 +1,213 @@
+import { mount } from "@vue/test-utils"
+import { expect, test } from "vitest"
+import { createVuetify } from "vuetify"
+import * as components from "vuetify/components"
+import * as directives from "vuetify/directives"
+import Hotkey from "@/atomics/Hotkey.vue"
+import SearchPopover from "@/components/SearchPopover.vue"
+
+const vuetify = createVuetify({
+ components,
+ directives,
+})
+
+global.ResizeObserver = require("resize-observer-polyfill")
+
+test("mounts correctly", () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Item 1", "Item 2", "Item 3"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ expect(wrapper.exists()).toBe(true)
+})
+
+test("toggles menu on button click", async () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Item 1", "Item 2", "Item 3"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ // assert that menu is not visible initially
+ expect(wrapper.vm.menu).toBe(false)
+
+ // find the button and trigger click event
+ await wrapper.find(".menu-activator").trigger("click")
+
+ // assert that menu is visible after click
+ expect(wrapper.vm.menu).toBe(true)
+})
+
+test("updates selectedItem when selectItem is called", async () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Item 1", "Item 2", "Item 3"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ // assert that selectedItem is initialValue initially
+ expect(wrapper.vm.selectedItem).toBe("Initial")
+
+ // call selectItem method
+ await wrapper.vm.selectItem("Item 2")
+
+ // assert that selectedItem is updated
+ expect(wrapper.vm.selectedItem).toBe("Item 2")
+})
+
+test("updates searchQuery when text field input changes", async () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Item 1", "Item 2", "Item 3"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ // find the button and trigger click event
+ await wrapper.find(".menu-activator").trigger("click")
+
+ // assert that searchQuery is empty initially
+ expect(wrapper.vm.searchQuery).toBe("")
+
+ // find the text field and trigger input event
+ await wrapper.findComponent({ name: "v-text-field" }).setValue("Item 2")
+
+ // assert that searchQuery is updated
+ expect(wrapper.vm.searchQuery).toBe("Item 2")
+})
+
+test("filters items based on searchQuery", async () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Apple", "Banana", "Cherry"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ // find the button and trigger click event
+ await wrapper.find(".menu-activator").trigger("click")
+
+ // assert that all items are present initially
+ expect(wrapper.vm.filteredItems).toEqual(["Apple", "Banana", "Cherry"])
+
+ // simulate user input in the text field
+ await wrapper.findComponent({ name: "v-text-field" }).setValue("an")
+
+ // assert that items are filtered based on searchQuery
+ expect(wrapper.vm.filteredItems).toEqual(["Banana"])
+})
+
+test("emits item-selected when an item is selected", async () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Apple", "Banana", "Cherry"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ // call selectItem method
+ await wrapper.vm.selectItem("Banana")
+
+ // assert that 'item-selected' event is emitted with the correct item
+ expect(wrapper.emitted()).toHaveProperty("item-selected")
+ expect(wrapper.emitted()["item-selected"]).toEqual([["Banana"]])
+})
+
+test("updates selectedItem when initialValue prop changes", async () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Apple", "Banana", "Cherry"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ // change initialValue prop
+ await wrapper.setProps({ initialValue: "New Value" })
+
+ // assert that selectedItem is updated
+ expect(wrapper.vm.selectedItem).toBe("New Value")
+})
+
+test("updates items when items prop changes", async () => {
+ const wrapper = mount(SearchPopover, {
+ props: {
+ hotkeys: ["a", "b", "c"],
+ initialValue: "Initial",
+ items: ["Apple", "Banana", "Cherry"],
+ label: "Label",
+ },
+ global: {
+ plugins: [vuetify],
+ components: {
+ Hotkey,
+ },
+ },
+ })
+
+ // change items prop
+ await wrapper.setProps({ items: ["Item 1", "Item 2", "Item 3"] })
+
+ // assert that items is updated
+ expect(wrapper.vm.items).toEqual(["Item 1", "Item 2", "Item 3"])
+})
From 15d7c8c8a648eab71e5f494eb61d88279c76f765 Mon Sep 17 00:00:00 2001
From: Will Sheldon <114631109+wssheldon@users.noreply.github.com>
Date: Tue, 5 Dec 2023 07:20:38 -0800
Subject: [PATCH 2/6] Require all fields but tags, fix Submit button validation
(#4080)
---
.../src/case/ReportSubmissionCard.vue | 130 +++++++++++-------
1 file changed, 82 insertions(+), 48 deletions(-)
diff --git a/src/dispatch/static/dispatch/src/case/ReportSubmissionCard.vue b/src/dispatch/static/dispatch/src/case/ReportSubmissionCard.vue
index 63ac38a36fc3..8f60ec7ad108 100644
--- a/src/dispatch/static/dispatch/src/case/ReportSubmissionCard.vue
+++ b/src/dispatch/static/dispatch/src/case/ReportSubmissionCard.vue
@@ -1,11 +1,10 @@
-
+
@@ -32,50 +31,48 @@
mdi-open-in-new
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -84,7 +81,7 @@
variant="flat"
block
:loading="loading"
- :disabled="!isValid.value"
+ :disabled="!formIsValid"
@click="report()"
>
Submit
@@ -128,6 +125,12 @@ export default {
return {
isSubmitted: false,
project_faq: null,
+ formIsValid: false,
+ titleValid: false,
+ descriptionValid: false,
+ projectValid: false,
+ caseTypeValid: false,
+ casePriorityValid: false,
}
},
@@ -148,6 +151,29 @@ export default {
]),
},
+ watch: {
+ title() {
+ this.titleValid = !!this.title
+ this.checkFormValidity()
+ },
+ description() {
+ this.descriptionValid = !!this.description
+ this.checkFormValidity()
+ },
+ project() {
+ this.projectValid = !!this.project
+ this.checkFormValidity()
+ },
+ case_type() {
+ this.caseTypeValid = !!this.case_type
+ this.checkFormValidity()
+ },
+ case_priority() {
+ this.casePriorityValid = !!this.case_priority
+ this.checkFormValidity()
+ },
+ },
+
methods: {
getFAQ() {
if (this.project) {
@@ -198,6 +224,14 @@ export default {
}
)
},
+ checkFormValidity() {
+ this.formIsValid =
+ this.titleValid &&
+ this.descriptionValid &&
+ this.projectValid &&
+ this.caseTypeValid &&
+ this.casePriorityValid
+ },
...mapActions("case_management", ["report", "get", "resetSelected"]),
},
From 248c74e693537b24d2ee373ea7e2541abfb2fecb Mon Sep 17 00:00:00 2001
From: Will Sheldon <114631109+wssheldon@users.noreply.github.com>
Date: Tue, 5 Dec 2023 09:37:57 -0800
Subject: [PATCH 3/6] Filter by project in SearchPopovers (#4082)
---
.../src/case/CaseAttributesDrawer.vue | 8 ++++-
.../priority/CasePrioritySearchPopover.vue | 36 +++++++++++++------
.../severity/CaseSeveritySearchPopover.vue | 36 +++++++++++++------
.../src/case/type/CaseTypeSearchPopover.vue | 36 +++++++++++++------
4 files changed, 82 insertions(+), 34 deletions(-)
diff --git a/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue b/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue
index 0cc7115f931c..f9277365039c 100644
--- a/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue
+++ b/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue
@@ -109,6 +109,7 @@ const handleResolutionUpdate = (newResolution) => {
@@ -121,6 +122,7 @@ const handleResolutionUpdate = (newResolution) => {
@@ -131,7 +133,11 @@ const handleResolutionUpdate = (newResolution) => {
Type
-
+
diff --git a/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue b/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue
index 0c72ad93bba2..44fc91515a32 100644
--- a/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue
+++ b/src/dispatch/static/dispatch/src/case/priority/CasePrioritySearchPopover.vue
@@ -1,5 +1,5 @@