Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
473955e
docs(4583): multi-agent plan for incident FK linkage
MuhammadKhalilzadeh Sep 10, 2026
f72c55f
feat(4583): migration adding model/project/assignee FKs to ai_inciden…
MuhammadKhalilzadeh Sep 10, 2026
d5f9dd8
feat(4583): add FK fields to IAIIncidentManagement interface
MuhammadKhalilzadeh Sep 10, 2026
675424e
feat(4583): add FK columns and joined display names to incident model
MuhammadKhalilzadeh Sep 10, 2026
244e741
feat(4583): incident queries - FK columns, list filters, joined names…
MuhammadKhalilzadeh Sep 10, 2026
28048c1
feat(4583): validate optional incident reference FKs (model/project/a…
MuhammadKhalilzadeh Sep 10, 2026
9e4378a
feat(4583): controller wiring - FK body fields, list filters, tenant …
MuhammadKhalilzadeh Sep 10, 2026
5a70cc0
test(4583): model FK serialization and reference FK validation specs
MuhammadKhalilzadeh Sep 10, 2026
175e57f
feat(4583): client incident model carries FK ids and joined names
MuhammadKhalilzadeh Sep 10, 2026
77f74f0
feat(4583): incident modal - affected model picker and owner assignee…
MuhammadKhalilzadeh Sep 10, 2026
a2db018
feat(4583): incident page - model/owner filter columns, modal FK init…
MuhammadKhalilzadeh Sep 10, 2026
cc2183e
feat(4583): incident table - affected model and owner columns
MuhammadKhalilzadeh Sep 10, 2026
9e30632
fix(4583): allow null FK values in incident form type
MuhammadKhalilzadeh Sep 10, 2026
e427a19
docs(4583): quality dashboard, acceptance verification, retrospective
MuhammadKhalilzadeh Sep 10, 2026
d743b3f
test(4583): tenant isolation coverage for ai_incident_managements
MuhammadKhalilzadeh Sep 10, 2026
fd17b72
fix(4583): normalize optional incident INSERT values to null (500 on …
MuhammadKhalilzadeh Sep 10, 2026
c07df6a
style(4583): prettier formatting for NewIncident modal
MuhammadKhalilzadeh Sep 10, 2026
e5201db
feat(4583): add DE/FR/ES translations for affected-model picker strings
MuhammadKhalilzadeh Sep 10, 2026
c2cc50d
fix: NewIncident drawer top margin and affected-model select overflow…
MuhammadKhalilzadeh Sep 11, 2026
703d435
fix: declare incident join aliases as VIRTUAL attributes so mapToMode…
MuhammadKhalilzadeh Sep 11, 2026
cccf1e3
feat: allow clearing affected model and owner pickers in NewIncident …
MuhammadKhalilzadeh Sep 11, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ export class AIIncidentManagementModel {
id!: number;
incident_id!: string;
ai_project!: string;
/** FK to model_inventories (issue #4583). */
model_inventory_id?: number | null;
/** FK to projects (issue #4583). */
project_id?: number | null;
/** FK to users — responsible owner (issue #4583). */
assignee_id?: number | null;
/** Joined display names (populated by the API). */
model_inventory_name?: string | null;
project_title?: string | null;
assignee_name?: string | null;
type!: string;
severity!: Severity;
status!: IncidentManagementStatus;
Expand All @@ -34,6 +44,12 @@ export class AIIncidentManagementModel {
this.id = data.id;
this.incident_id = data.incident_id;
this.ai_project = data.ai_project;
this.model_inventory_id = data.model_inventory_id ?? null;
this.project_id = data.project_id ?? null;
this.assignee_id = data.assignee_id ?? null;
this.model_inventory_name = data.model_inventory_name ?? null;
this.project_title = data.project_title ?? null;
this.assignee_name = data.assignee_name ?? null;
this.type = data.type;
this.severity = data.severity;
this.status = data.status;
Expand Down
9 changes: 9 additions & 0 deletions Clients/src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,9 @@ export const translations: Record<string, Record<string, string>> = {
"AI project": "KI-Projekt",
"AI Project": "KI-Projekt",
"AI PROJECT": "KI-PROJEKT",
"Affected model": "Betroffenes Modell",
"AFFECTED MODEL": "BETROFFENES MODELL",
"Select model from inventory": "Modell aus dem Inventar auswählen",
"Project": "Projekt",
"Create a new use case by filling in the following details":
"Erstellen Sie einen neuen Anwendungsfall, indem Sie die folgenden Angaben ausfüllen",
Expand Down Expand Up @@ -10619,6 +10622,9 @@ export const translations: Record<string, Record<string, string>> = {
"Deadline view": "Vue des échéances",
"Operations view": "Vue opérationnelle",
"AI PROJECT": "PROJET IA",
"Affected model": "Modèle affecté",
"AFFECTED MODEL": "MODÈLE AFFECTÉ",
"Select model from inventory": "Sélectionner un modèle dans l'inventaire",
"How deadlines work": "Comment fonctionnent les échéances",
"Open any task and set a due date. It will automatically appear in the correct deadline group on this view.":
"Ouvrez une tâche et définissez une date d'échéance. Elle apparaîtra automatiquement dans le bon groupe d'échéance sur cette vue.",
Expand Down Expand Up @@ -19337,6 +19343,9 @@ export const translations: Record<string, Record<string, string>> = {
"AI project": "Proyecto de IA",
"AI Project": "Proyecto de IA",
"AI PROJECT": "PROYECTO DE IA",
"Affected model": "Modelo afectado",
"AFFECTED MODEL": "MODELO AFECTADO",
"Select model from inventory": "Seleccionar modelo del inventario",
"Project": "Proyecto",
"How deadlines work": "Cómo funcionan los plazos",
"Tasks in a filterable table": "Tareas en una tabla filtrable",
Expand Down
114 changes: 111 additions & 3 deletions Clients/src/presentation/components/Modals/NewIncident/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ interface SideDrawerIncidentProps {
export interface NewIncidentFormValues {
incident_id?: string;
ai_project: string;
/** FK to projects — set alongside ai_project when a use case is picked (issue #4583). */
project_id?: number | string | null;
/** FK to model_inventories — affected model (issue #4583). */
model_inventory_id?: number | string | null;
/** FK to users — responsible owner (issue #4583). */
assignee_id?: number | string | null;
type: string;
severity: Severity;
status: IncidentManagementStatus;
Expand All @@ -74,6 +80,9 @@ export interface NewIncidentFormValues {

const initialState: NewIncidentFormValues = {
ai_project: "",
project_id: "",
model_inventory_id: "",
assignee_id: "",
type: "",
severity: Severity.MINOR,
status: IncidentManagementStatus.OPEN,
Expand Down Expand Up @@ -133,6 +142,7 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
const [values, setValues] = useState<NewIncidentFormValues>(initialData || initialState);
const [users, setUsers] = useState<User[]>([]);
const [, setIsLoadingUsers] = useState(false);
const [modelInventories, setModelInventories] = useState<any[]>([]);
const [activeTab, setActiveTab] = useState("details");

const validators = useMemo(
Expand Down Expand Up @@ -164,6 +174,7 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
useEffect(() => {
if (isOpen) {
fetchUsers();
fetchModelInventories();
}
}, [isOpen]);

Expand All @@ -188,6 +199,16 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
}
};

// Fetch model inventory for the affected-model picker (issue #4583)
const fetchModelInventories = async () => {
try {
const response = await getAllEntities({ routeUrl: "/modelInventory" });
if (response?.data) setModelInventories(response.data);
} catch (error) {
console.error("Error fetching model inventory:", error);
}
};

const projectOptions = useMemo(() => {
// Use a Set to track unique project titles
const seen = new Set<string>();
Expand All @@ -212,6 +233,45 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
[users],
);

// Owner/assignee options keyed by user id (issue #4583)
const assigneeOptions = useMemo(
() => [
// "(none)" clears the owner FK — '' is normalised to null on save (issue #4583)
{ _id: "" as string | number, name: "(none)" },
...users.map((u) => ({ _id: u.id, name: `${u.name} ${u.surname}` })),
],
[users],
);

// Affected-model options keyed by model inventory id (issue #4583)
const modelInventoryOptions = useMemo(
() => [
// "(none)" clears the affected-model FK — '' is normalised to null on save (issue #4583)
{ _id: "" as string | number, name: "(none)" },
...modelInventories.map((m) => ({
_id: m.id,
name: [m.provider, m.model].filter(Boolean).join(" "),
})),
],
[modelInventories],
);

// Use-case select keeps the free-text ai_project (backward compatible) and
// also records the project FK (issue #4583).
const handleProjectChange = useCallback(
(e: any) => {
const title = e.target.value;
const project = (approvedProjects as any[]).find((p) => p.project_title === title);
setValues((prev) => ({
...prev,
ai_project: title,
project_id: project?.id ?? "",
}));
clearFieldError("ai_project");
},
[approvedProjects, clearFieldError],
);

const handleFieldChange = useCallback(
(prop: keyof NewIncidentFormValues) => (e: any) => {
const value = e.target.value;
Expand Down Expand Up @@ -262,7 +322,14 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
const handleSaveIncident = (e?: React.FormEvent) => {
if (e) e.preventDefault();
if (validateAll(values)) {
onSuccess?.(values);
// Normalize optional FK pickers to integer ids or null (issue #4583)
const toId = (v: unknown) => (v === undefined || v === null || v === "" ? null : Number(v));
onSuccess?.({
...values,
project_id: toId(values.project_id),
model_inventory_id: toId(values.model_inventory_id),
assignee_id: toId(values.assignee_id),
});
handleClose();
}
};
Expand All @@ -272,7 +339,18 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
useModalKeyHandling({ isOpen, onClose: handleClose });

return (
<Drawer anchor="right" open={isOpen} onClose={handleClose}>
<Drawer
anchor="right"
open={isOpen}
onClose={handleClose}
sx={{
"margin": 0,
"& .MuiDrawer-paper": {
margin: 0,
borderRadius: 0,
},
}}
>
<Stack
sx={{
width: 700,
Expand Down Expand Up @@ -377,7 +455,7 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
placeholder="Select AI use case or framework"
items={projectOptions}
value={values.ai_project}
onChange={handleFieldChange("ai_project")}
onChange={handleProjectChange}
error={errors.ai_project}
isRequired
sx={{ flex: 1 }}
Expand Down Expand Up @@ -432,6 +510,36 @@ const SideDrawerIncident: FC<SideDrawerIncidentProps> = ({
</Stack>
</Stack>

{/* Row: Affected model + Owner (issue #4583 FK pickers) */}
<Stack direction={"row"} gap={theme.spacing(8)} sx={{ mt: 2 }}>
<Stack sx={{ gap: 3, width: "50%", minWidth: 0 }}>
<SelectComponent
id="model_inventory_id"
label="Affected model"
placeholder="Select model from inventory"
items={modelInventoryOptions}
value={values.model_inventory_id ?? ""}
onChange={handleFieldChange("model_inventory_id")}
isOptional
sx={{ flex: 1 }}
disabled={isViewMode}
/>
</Stack>
<Stack sx={{ gap: 3, width: "50%", minWidth: 0 }}>
<SelectComponent
id="assignee_id"
label="Owner"
placeholder="Select owner"
items={assigneeOptions}
value={values.assignee_id ?? ""}
onChange={handleFieldChange("assignee_id")}
isOptional
sx={{ flex: 1 }}
disabled={isViewMode}
/>
</Stack>
</Stack>

{/* Row: Occurred Date + Detected Date */}
<Stack direction={"row"} gap={theme.spacing(8)} sx={{ mt: 2 }}>
<Suspense fallback={<div>Loading...</div>}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const cellStyle = singleTheme.tableStyles.primary.body.cell;
const TABLE_COLUMNS: StandardColumn[] = [
{ id: "incident_id", label: "INCIDENT ID", sortable: true },
{ id: "ai_project", label: "AI PROJECT", sortable: true },
{ id: "model_inventory_name", label: "AFFECTED MODEL", sortable: true },
{ id: "assignee_name", label: "OWNER", sortable: true },
{ id: "type", label: "TYPE", sortable: true },
{ id: "severity", label: "SEVERITY", sortable: true },
{ id: "status", label: "STATUS", sortable: true },
Expand Down Expand Up @@ -94,6 +96,14 @@ const IncidentTable: React.FC<IncidentTableProps> = ({
aValue = a.ai_project?.toLowerCase() || "";
bValue = b.ai_project?.toLowerCase() || "";
break;
case "model_inventory_name":
aValue = a.model_inventory_name?.toLowerCase() || "";
bValue = b.model_inventory_name?.toLowerCase() || "";
break;
case "assignee_name":
aValue = a.assignee_name?.toLowerCase() || "";
bValue = b.assignee_name?.toLowerCase() || "";
break;
case "type":
aValue = a.type?.toLowerCase() || "";
bValue = b.type?.toLowerCase() || "";
Expand Down Expand Up @@ -190,6 +200,30 @@ const IncidentTable: React.FC<IncidentTableProps> = ({
<TooltipCell value={incident.ai_project} />
</TableCell>
)}
{isVisible("model_inventory_name") && (
<TableCell
sx={{
...cellStyle,
backgroundColor:
sortConfig.key === "model_inventory_name"
? "background.surface"
: "inherit",
}}
>
<TooltipCell value={incident.model_inventory_name} />
</TableCell>
)}
{isVisible("assignee_name") && (
<TableCell
sx={{
...cellStyle,
backgroundColor:
sortConfig.key === "assignee_name" ? "background.surface" : "inherit",
}}
>
<TooltipCell value={incident.assignee_name} />
</TableCell>
)}
{isVisible("type") && (
<TableCell
sx={{
Expand Down
Loading
Loading