Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 50 additions & 18 deletions src/dispatch/static/dispatch/src/tag/TagPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@
</div>
</div>

<!-- No project error banner -->
<div v-if="noProjectError" class="error-banner mb-3">
<div class="error-content">
<v-icon size="18" color="#f57f17" class="mr-2">mdi-alert</v-icon>
<span class="error-message">{{ noProjectError }}</span>
</div>
</div>

<!-- Suggestions panel: Show when generated (expanded or collapsed) -->
<div
v-if="suggestionsGenerated && tagSuggestions.length > 0 && props.showGenAISuggestions"
Expand Down Expand Up @@ -273,6 +281,7 @@ const isDropdownOpen = ref(false)
const snackbar = ref(false)
const suggestionsExpanded = ref(false)
const error = ref(null)
const noProjectError = ref(null)

const props = defineProps({
modelValue: {
Expand Down Expand Up @@ -352,21 +361,29 @@ function validateTags(value) {
return
}

const project_id = props.project?.id || 0
var all_tags_in_project = false
if (project_id) {
all_tags_in_project = value.every((tag) => tag.project?.id == project_id)
} else {
const project_name = props.project?.name
if (!project_name) {
error.value = true
dummyText.value += " "
return
}
all_tags_in_project = value.every((tag) => tag.project?.name == project_name)
// Handle both single project object and array of projects
const projects = Array.isArray(props.project) ? props.project : [props.project]
const validProjects = projects.filter((p) => p && p.name)

if (!validProjects.length) {
error.value = true
dummyText.value += " "
return
}

if (all_tags_in_project) {
// Check if all tags belong to any of the selected projects
const all_tags_in_projects = value.every((tag) => {
return validProjects.some((project) => {
if (project.id && tag.project?.id) {
return tag.project.id === project.id
} else if (project.name && tag.project?.name) {
return tag.project.name === project.name
}
return false
})
})

if (all_tags_in_projects) {
const requiredSelected = are_required_tags_selected(value)

if (!requiredSelected) {
Expand All @@ -380,18 +397,31 @@ function validateTags(value) {
error.value = null
}
} else {
error.value = "Only tags in selected project are allowed"
error.value = "Only tags in selected projects are allowed"
}
dummyText.value += " "
}

// Methods
const fetchData = async () => {
if (!props.project) {
// Handle both single project object and array of projects
const projects = Array.isArray(props.project) ? props.project : [props.project]
const validProjects = projects.filter((p) => p && p.name)

if (!validProjects.length) {
// Show error message when no projects are selected
store.commit("tag/SET_TABLE_ROWS", { items: [], total: 0 })
store.commit("tag/SET_GROUPS", {})
noProjectError.value = "You must select a project to view tags"
return
}

// Clear any previous error when we have valid projects
noProjectError.value = null

// Let the store handle all the logic for single or multiple projects
await store.dispatch("tag/fetchTags", {
project: props.project,
project: validProjects,
model: props.model,
})
}
Expand All @@ -401,9 +431,11 @@ const fetchTagTypes = async () => {
}

const generateSuggestions = async () => {
if (!props.project?.id) return
// Handle both single project object and array of projects
const project = Array.isArray(props.project) ? props.project[0] : props.project
if (!project?.id) return
await store.dispatch("tag/generateSuggestions", {
projectId: props.project.id,
projectId: project.id,
modelId: props.modelId,
modelType: props.modelType,
})
Expand Down
126 changes: 92 additions & 34 deletions src/dispatch/static/dispatch/src/tag/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,43 +282,108 @@ const actions = {
return tags
},

async fetchTags({ commit }, { project, model }) {
if (!project) return
async fetchTags({ commit, dispatch }, { project, model }) {
// Handle both single project object and array of projects
const projects = Array.isArray(project) ? project : [project]
const validProjects = projects.filter((p) => p && p.name)

if (!validProjects.length) {
commit("SET_TABLE_ROWS", { items: [], total: 0 })
commit("SET_GROUPS", {})
return []
}

commit("SET_LOADING", true)

let filterOptions = {
q: null,
itemsPerPage: 500,
sortBy: ["tag_type.name"],
descending: [false],
filters: {
project: [{ model: "Project", field: "name", op: "==", value: project.name }],
tagFilter: [{ model: "Tag", field: "discoverable", op: "==", value: "true" }],
},
}
try {
let relevantTagTypeIds = []

if (model) {
filterOptions.filters.tagTypeFilter = [
{ model: "TagType", field: "discoverable_" + model, op: "==", value: "true" },
]
}
// If model is specified, first fetch the relevant TagType IDs
if (model) {
const tagTypeFilterOptions = {
filter: JSON.stringify([
{
and: [{ model: "TagType", field: "discoverable_" + model, op: "==", value: "true" }],
},
]),
itemsPerPage: -1,
fields: JSON.stringify(["id"]),
}

filterOptions = SearchUtils.createParametersFromTableOptions(filterOptions)
const tagTypeResponse = await TagTypeApi.getAll(tagTypeFilterOptions)
relevantTagTypeIds = tagTypeResponse.data.items.map((tt) => tt.id)

try {
const response = await TagApi.getAll(filterOptions)
commit("SET_TABLE_ROWS", response.data)
commit("SET_GROUPS", convertData(response.data.items))
if (!relevantTagTypeIds.length) {
commit("SET_TABLE_ROWS", { items: [], total: 0 })
commit("SET_GROUPS", {})
commit("SET_LOADING", false)
return []
}
}

// Fetch tags for each project and combine them
const allTags = []
for (const singleProject of validProjects) {
const projectTags = await dispatch("fetchTagsForSingleProject", {
project: singleProject,
relevantTagTypeIds,
model,
})
if (projectTags) {
allTags.push(...projectTags)
}
}
// Update the store with combined results
commit("SET_TABLE_ROWS", { items: allTags, total: allTags.length })
commit("SET_GROUPS", convertData(allTags))
commit("SET_LOADING", false)
return response.data.items
return allTags
} catch (error) {
console.error("Error fetching tags:", error)
commit("SET_LOADING", false)
throw error
}
},

async fetchTagsForSingleProject(_, { project, relevantTagTypeIds, model }) {
// Build the tag filter options using the same direct approach as tagTypeFilterOptions
let baseFilters = [{ model: "Tag", field: "discoverable", op: "==", value: "true" }]

// Add project filter if project is defined
if (project && project.name) {
baseFilters.unshift({ model: "Project", field: "name", op: "==", value: project.name })
}

let tagFilterOptions = {
filter: JSON.stringify([
{
and: baseFilters,
},
]),
itemsPerPage: 500,
sortBy: ["tag_type.name"],
sortDesc: [false],
}

// If we have relevant tag type IDs, add the filter
if (model && relevantTagTypeIds.length > 0) {
baseFilters.push({
model: "Tag",
field: "tag_type_id",
op: "in",
value: relevantTagTypeIds,
})
tagFilterOptions.filter = JSON.stringify([
{
and: baseFilters,
},
])
}

const response = await TagApi.getAll(tagFilterOptions)
return response.data.items
},

async fetchTagTypes({ commit }) {
try {
const resp = await TagTypeApi.getAll({ itemsPerPage: 5000 })
Expand Down Expand Up @@ -393,6 +458,10 @@ const actions = {
getTagType({ state }, tagTypeId) {
return state.tagTypes[tagTypeId] || {}
},

convertDataAndSetGroups({ commit }, data) {
commit("SET_GROUPS", convertData(data))
},
}

function areRequiredTagsSelected(sel, tagTypes) {
Expand Down Expand Up @@ -476,17 +545,6 @@ const mutations = {
// Helper function for converting data
function convertData(data) {
return data.reduce((r, a) => {
const tagType = a.tag_type
const hasAnyDiscoverability =
tagType.discoverable_incident ||
tagType.discoverable_case ||
tagType.discoverable_signal ||
tagType.discoverable_query ||
tagType.discoverable_source ||
tagType.discoverable_document

if (!hasAnyDiscoverability) return r

if (!r[a.tag_type.id]) {
r[a.tag_type.id] = {
id: a.tag_type.id,
Expand Down
Loading