Skip to content

Commit 230ae11

Browse files
committed
Add delete measure
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
1 parent 76b6df7 commit 230ae11

File tree

10 files changed

+570
-25
lines changed

10 files changed

+570
-25
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
8+
### Added
9+
10+
- Add delete measure in the UI and GraphQL API
11+
712
### Removed
813

914
- Remove `importance` field from measure as it's not used anymore

apps/console/src/pages/organizations/mesures/MesureView.tsx

Lines changed: 108 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
useMutation,
1616
fetchQuery,
1717
useRelayEnvironment,
18+
ConnectionHandler,
1819
} from "react-relay";
1920
import {
2021
AlertTriangle,
@@ -106,6 +107,7 @@ import { MesureViewCreateControlMappingMutation } from "./__generated__/MesureVi
106107
import { MesureViewDeleteControlMappingMutation } from "./__generated__/MesureViewDeleteControlMappingMutation.graphql";
107108
import { MesureViewRisksQuery$data } from "./__generated__/MesureViewRisksQuery.graphql";
108109
import { MesureViewRisksQuery } from "./__generated__/MesureViewRisksQuery.graphql";
110+
import { MesureViewDeleteMesureMutation } from "./__generated__/MesureViewDeleteMesureMutation.graphql";
109111

110112
// Function to format ISO8601 duration to human-readable format
111113
const formatDuration = (isoDuration: string): string => {
@@ -326,6 +328,17 @@ const updateMesureStateMutation = graphql`
326328
}
327329
`;
328330

331+
const deleteMesureMutation = graphql`
332+
mutation MesureViewDeleteMesureMutation(
333+
$input: DeleteMesureInput!
334+
$connections: [ID!]!
335+
) {
336+
deleteMesure(input: $input) {
337+
deletedMesureId @deleteEdge(connections: $connections)
338+
}
339+
}
340+
`;
341+
329342
const organizationQuery = graphql`
330343
query MesureViewOrganizationQuery($organizationId: ID!) {
331344
organization: node(id: $organizationId) {
@@ -477,11 +490,17 @@ function MesureViewContent({
477490
mesureViewQuery,
478491
queryRef
479492
);
480-
const { toast } = useToast();
481-
const { organizationId, mesureId } = useParams();
482493
const navigate = useNavigate();
494+
const { toast } = useToast();
495+
const { organizationId, mesureId } = useParams<{
496+
organizationId: string;
497+
mesureId: string;
498+
}>();
483499
const environment = useRelayEnvironment();
484500

501+
const [commitDeleteMesure, isDeletingMesure] = useMutation<MesureViewDeleteMesureMutation>(deleteMesureMutation);
502+
const [isDeleteMesureOpen, setIsDeleteMesureOpen] = useState(false);
503+
485504
// Add state for main content tabs
486505
const [mainContentTab, setMainContentTab] = useState<string>("tasks");
487506

@@ -1815,32 +1834,70 @@ function MesureViewContent({
18151834
return "Very High";
18161835
};
18171836

1837+
const handleDeleteMesure = () => {
1838+
setIsDeleteMesureOpen(true);
1839+
};
1840+
1841+
const confirmDeleteMesure = () => {
1842+
const connectionId = ConnectionHandler.getConnectionID(
1843+
organizationId!,
1844+
"MesureListView_mesures"
1845+
);
1846+
1847+
commitDeleteMesure({
1848+
variables: {
1849+
connections: [connectionId],
1850+
input: {
1851+
mesureId: mesureId!,
1852+
},
1853+
},
1854+
onCompleted: () => {
1855+
toast({
1856+
title: "Mesure deleted",
1857+
description: "Mesure has been deleted successfully.",
1858+
});
1859+
navigate(`/organizations/${organizationId}/mesures`);
1860+
},
1861+
onError: (error) => {
1862+
toast({
1863+
title: "Error deleting mesure",
1864+
description: error.message,
1865+
variant: "destructive",
1866+
});
1867+
},
1868+
});
1869+
};
1870+
18181871
return (
18191872
<PageTemplate
1820-
title={data.mesure.name ?? ""}
1873+
title={data.mesure?.name || "Mesure"}
1874+
description={data.mesure?.description}
18211875
actions={
1822-
<div className="flex items-center gap-2">
1823-
<Button onClick={handleEditMesure}>Edit Mesure</Button>
1824-
<Select
1825-
defaultValue={data.mesure.state}
1826-
onValueChange={handleMesureStateChange}
1876+
<div className="flex gap-2">
1877+
<Button
1878+
variant="outline"
1879+
onClick={handleEditMesure}
18271880
>
1828-
<SelectTrigger className="w-[160px] h-10 text-sm">
1829-
<div
1830-
className={`${getStateColor(
1831-
data.mesure.state
1832-
)} px-2 py-0.5 rounded-sm text-sm w-full text-center`}
1833-
>
1834-
{formatState(data.mesure.state)}
1835-
</div>
1836-
</SelectTrigger>
1837-
<SelectContent>
1838-
<SelectItem value="NOT_STARTED">Not Started</SelectItem>
1839-
<SelectItem value="IN_PROGRESS">In Progress</SelectItem>
1840-
<SelectItem value="IMPLEMENTED">Implemented</SelectItem>
1841-
<SelectItem value="NOT_APPLICABLE">Not Applicable</SelectItem>
1842-
</SelectContent>
1843-
</Select>
1881+
Edit
1882+
</Button>
1883+
<select
1884+
value={data.mesure?.state || ""}
1885+
onChange={(e) => handleMesureStateChange(e.target.value)}
1886+
className="rounded-full cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-active-b disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-low-b hover:bg-h-tertiary-bg active:bg-p-tertiary-bg focus:bg-tertiary-bg shadow-xs px-2"
1887+
>
1888+
<option value="">Select state</option>
1889+
<option value="NOT_STARTED">Not Started</option>
1890+
<option value="IN_PROGRESS">In Progress</option>
1891+
<option value="NOT_APPLICABLE">Not Applicable</option>
1892+
<option value="IMPLEMENTED">Implemented</option>
1893+
</select>
1894+
<Button
1895+
variant="destructive"
1896+
onClick={() => handleDeleteMesure()}
1897+
disabled={isDeletingMesure}
1898+
>
1899+
{isDeletingMesure ? "Deleting..." : "Delete"}
1900+
</Button>
18441901
</div>
18451902
}
18461903
>
@@ -3598,6 +3655,33 @@ function MesureViewContent({
35983655
</DialogFooter>
35993656
</DialogContent>
36003657
</Dialog>
3658+
3659+
{/* Delete Mesure Confirmation Dialog */}
3660+
<Dialog open={isDeleteMesureOpen} onOpenChange={setIsDeleteMesureOpen}>
3661+
<DialogContent>
3662+
<DialogHeader>
3663+
<DialogTitle>Delete Mesure</DialogTitle>
3664+
<DialogDescription>
3665+
Are you sure you want to delete this mesure? This action cannot be undone.
3666+
</DialogDescription>
3667+
</DialogHeader>
3668+
<DialogFooter>
3669+
<Button
3670+
variant="outline"
3671+
onClick={() => setIsDeleteMesureOpen(false)}
3672+
>
3673+
Cancel
3674+
</Button>
3675+
<Button
3676+
variant="destructive"
3677+
onClick={confirmDeleteMesure}
3678+
disabled={isDeletingMesure}
3679+
>
3680+
{isDeletingMesure ? "Deleting..." : "Delete"}
3681+
</Button>
3682+
</DialogFooter>
3683+
</DialogContent>
3684+
</Dialog>
36013685
</PageTemplate>
36023686
);
36033687
}

apps/console/src/pages/organizations/mesures/__generated__/MesureViewDeleteMesureMutation.graphql.ts

Lines changed: 132 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/coredata/mesure.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,22 @@ WHERE %s
433433
_, err := conn.Exec(ctx, q, args)
434434
return err
435435
}
436+
437+
func (m *Mesure) Delete(
438+
ctx context.Context,
439+
conn pg.Conn,
440+
scope Scoper,
441+
) error {
442+
q := `
443+
DELETE FROM mesures
444+
WHERE %s
445+
AND id = @mesure_id
446+
`
447+
q = fmt.Sprintf(q, scope.SQLFragment())
448+
449+
args := pgx.StrictNamedArgs{"mesure_id": m.ID}
450+
maps.Copy(args, scope.SQLArguments())
451+
452+
_, err := conn.Exec(ctx, q, args)
453+
return err
454+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ALTER TABLE evidences DROP CONSTRAINT evidences_task_id_fkey;
2+
3+
ALTER TABLE evidences
4+
ADD CONSTRAINT evidences_task_id_fkey
5+
FOREIGN KEY (task_id)
6+
REFERENCES tasks(id)
7+
ON DELETE CASCADE;

pkg/probo/mesure_service.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,18 @@ func (s MesureService) Create(
371371

372372
return mesure, nil
373373
}
374+
375+
func (s MesureService) Delete(
376+
ctx context.Context,
377+
mesureID gid.GID,
378+
) error {
379+
return s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
380+
mesure := &coredata.Mesure{ID: mesureID}
381+
382+
if err := mesure.Delete(ctx, conn, s.svc.scope); err != nil {
383+
return fmt.Errorf("cannot delete mesure: %w", err)
384+
}
385+
386+
return nil
387+
})
388+
}

pkg/server/api/console/v1/schema.graphql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,7 @@ type Mutation {
895895
createMesure(input: CreateMesureInput!): CreateMesurePayload!
896896
updateMesure(input: UpdateMesureInput!): UpdateMesurePayload!
897897
importMesure(input: ImportMesureInput!): ImportMesurePayload!
898+
deleteMesure(input: DeleteMesureInput!): DeleteMesurePayload!
898899

899900
# Control mutations
900901
createControlMesureMapping(
@@ -1481,3 +1482,11 @@ input CreateVendorRiskAssessmentInput {
14811482
type CreateVendorRiskAssessmentPayload {
14821483
vendorRiskAssessmentEdge: VendorRiskAssessmentEdge!
14831484
}
1485+
1486+
input DeleteMesureInput {
1487+
mesureId: ID!
1488+
}
1489+
1490+
type DeleteMesurePayload {
1491+
deletedMesureId: ID!
1492+
}

0 commit comments

Comments
 (0)