fix: make project template inheritance granular and safe
This commit is contained in:
@@ -4,6 +4,7 @@ import { computed, reactive } from 'vue';
|
||||
import ProjectNotificationSettingsController from '@/actions/App/Http/Controllers/ProjectNotificationSettingsController';
|
||||
import Heading from '@/components/Heading.vue';
|
||||
import InputError from '@/components/InputError.vue';
|
||||
import TemplateInheritancePanel from '@/components/projects/TemplateInheritancePanel.vue';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -89,7 +90,8 @@ const breadcrumbs: BreadcrumbItem[] = [
|
||||
|
||||
let filterStateCounter = 0;
|
||||
|
||||
const nextFilterStateId = (): string => `notification-filter-${filterStateCounter++}`;
|
||||
const nextFilterStateId = (): string =>
|
||||
`notification-filter-${filterStateCounter++}`;
|
||||
|
||||
const parseInFilterValues = (value: string): string[] =>
|
||||
value
|
||||
@@ -100,7 +102,8 @@ const parseInFilterValues = (value: string): string[] =>
|
||||
const supportsFilterValue = (operator: string): boolean =>
|
||||
!['exists', 'not_exists'].includes(operator);
|
||||
|
||||
const supportsArrayFilterValue = (operator: string): boolean => operator === 'in';
|
||||
const supportsArrayFilterValue = (operator: string): boolean =>
|
||||
operator === 'in';
|
||||
|
||||
const createFilter = (): NotificationFilterFormState => ({
|
||||
attribute: defaultFilterAttribute,
|
||||
@@ -159,8 +162,9 @@ const resetCreateFilters = (): void => {
|
||||
createFilterState.filters = [];
|
||||
};
|
||||
|
||||
const editFilters = (notificationActionId: number): NotificationFilterFormState[] =>
|
||||
editFilterState[notificationActionId] ?? [];
|
||||
const editFilters = (
|
||||
notificationActionId: number,
|
||||
): NotificationFilterFormState[] => editFilterState[notificationActionId] ?? [];
|
||||
|
||||
const addEditFilter = (notificationActionId: number): void => {
|
||||
editFilterState[notificationActionId] = [
|
||||
@@ -169,10 +173,13 @@ const addEditFilter = (notificationActionId: number): void => {
|
||||
];
|
||||
};
|
||||
|
||||
const removeEditFilter = (notificationActionId: number, index: number): void => {
|
||||
editFilterState[notificationActionId] = editFilters(notificationActionId).filter(
|
||||
(_, candidateIndex) => candidateIndex !== index,
|
||||
);
|
||||
const removeEditFilter = (
|
||||
notificationActionId: number,
|
||||
index: number,
|
||||
): void => {
|
||||
editFilterState[notificationActionId] = editFilters(
|
||||
notificationActionId,
|
||||
).filter((_, candidateIndex) => candidateIndex !== index);
|
||||
};
|
||||
|
||||
const createSerializedFilters = computed<string>(() =>
|
||||
@@ -305,31 +312,11 @@ const notificationActionTimestamp = (
|
||||
{{ status }}
|
||||
</div>
|
||||
|
||||
<Card v-if="templateState?.inheritsFromTemplate">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
Template inheritance: {{ templateState.templateName }}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Notification rules can remain inherited from the template or diverge here as project-level overrides.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-wrap gap-2">
|
||||
<Badge
|
||||
v-for="area in templateState.inheritedAreas"
|
||||
:key="`inherited-${area.value}`"
|
||||
variant="outline"
|
||||
>
|
||||
{{ area.label }} · inherited
|
||||
</Badge>
|
||||
<Badge
|
||||
v-for="area in templateState.overriddenAreas"
|
||||
:key="`overridden-${area.value}`"
|
||||
>
|
||||
{{ area.label }} · overridden
|
||||
</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TemplateInheritancePanel
|
||||
:areas="['notification_actions']"
|
||||
:project-id="project.id"
|
||||
:template-state="templateState"
|
||||
/>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-[24rem_minmax(0,1fr)]">
|
||||
<div class="space-y-6">
|
||||
@@ -543,7 +530,10 @@ const notificationActionTimestamp = (
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="createFilterState.filters.length === 0"
|
||||
v-if="
|
||||
createFilterState.filters.length ===
|
||||
0
|
||||
"
|
||||
class="text-muted-foreground text-sm"
|
||||
>
|
||||
No filters configured. This action runs
|
||||
@@ -552,8 +542,7 @@ const notificationActionTimestamp = (
|
||||
|
||||
<div
|
||||
v-for="(
|
||||
filter,
|
||||
filterIndex
|
||||
filter, filterIndex
|
||||
) in createFilterState.filters"
|
||||
:key="filter.id"
|
||||
class="grid gap-3 rounded-md border p-3"
|
||||
@@ -572,8 +561,12 @@ const notificationActionTimestamp = (
|
||||
>
|
||||
<option
|
||||
v-for="filterAttributeOption in filterAttributeOptions"
|
||||
:key="filterAttributeOption.value"
|
||||
:value="filterAttributeOption.value"
|
||||
:key="
|
||||
filterAttributeOption.value
|
||||
"
|
||||
:value="
|
||||
filterAttributeOption.value
|
||||
"
|
||||
>
|
||||
{{
|
||||
filterAttributeOption.label
|
||||
@@ -595,8 +588,12 @@ const notificationActionTimestamp = (
|
||||
>
|
||||
<option
|
||||
v-for="filterOperatorOption in filterOperatorOptions"
|
||||
:key="filterOperatorOption.value"
|
||||
:value="filterOperatorOption.value"
|
||||
:key="
|
||||
filterOperatorOption.value
|
||||
"
|
||||
:value="
|
||||
filterOperatorOption.value
|
||||
"
|
||||
>
|
||||
{{
|
||||
filterOperatorOption.label
|
||||
@@ -607,7 +604,11 @@ const notificationActionTimestamp = (
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="supportsFilterValue(filter.operator)"
|
||||
v-if="
|
||||
supportsFilterValue(
|
||||
filter.operator,
|
||||
)
|
||||
"
|
||||
class="grid gap-2"
|
||||
>
|
||||
<Label
|
||||
@@ -662,7 +663,9 @@ const notificationActionTimestamp = (
|
||||
</div>
|
||||
|
||||
<InputError
|
||||
:message="notificationFilterError(errors)"
|
||||
:message="
|
||||
notificationFilterError(errors)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1029,8 +1032,7 @@ const notificationActionTimestamp = (
|
||||
|
||||
<div
|
||||
v-for="(
|
||||
filter,
|
||||
filterIndex
|
||||
filter, filterIndex
|
||||
) in editFilters(notificationAction.id)"
|
||||
:key="filter.id"
|
||||
class="grid gap-3 rounded-md border p-3"
|
||||
@@ -1049,8 +1051,12 @@ const notificationActionTimestamp = (
|
||||
>
|
||||
<option
|
||||
v-for="filterAttributeOption in filterAttributeOptions"
|
||||
:key="filterAttributeOption.value"
|
||||
:value="filterAttributeOption.value"
|
||||
:key="
|
||||
filterAttributeOption.value
|
||||
"
|
||||
:value="
|
||||
filterAttributeOption.value
|
||||
"
|
||||
>
|
||||
{{
|
||||
filterAttributeOption.label
|
||||
@@ -1072,8 +1078,12 @@ const notificationActionTimestamp = (
|
||||
>
|
||||
<option
|
||||
v-for="filterOperatorOption in filterOperatorOptions"
|
||||
:key="filterOperatorOption.value"
|
||||
:value="filterOperatorOption.value"
|
||||
:key="
|
||||
filterOperatorOption.value
|
||||
"
|
||||
:value="
|
||||
filterOperatorOption.value
|
||||
"
|
||||
>
|
||||
{{
|
||||
filterOperatorOption.label
|
||||
@@ -1084,7 +1094,11 @@ const notificationActionTimestamp = (
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="supportsFilterValue(filter.operator)"
|
||||
v-if="
|
||||
supportsFilterValue(
|
||||
filter.operator,
|
||||
)
|
||||
"
|
||||
class="grid gap-2"
|
||||
>
|
||||
<Label
|
||||
@@ -1144,7 +1158,9 @@ const notificationActionTimestamp = (
|
||||
</div>
|
||||
|
||||
<InputError
|
||||
:message="notificationFilterError(errors)"
|
||||
:message="
|
||||
notificationFilterError(errors)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user