Rename State of Applicability to Statement of Applicability
Rename the entity across the full stack: database table (states_of_applicability → statements_of_applicability), Go model, GraphQL types, MCP specification, CLI commands, frontend components, routes, and display labels. Includes a migration to rename the table and its foreign key column. Widen sidebar from 260px to 280px to fit the longer label. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -34,15 +34,15 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { useFragment } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
import type { LinkedStatesOfApplicabilityCardFragment$key } from "#/__generated__/core/LinkedStatesOfApplicabilityCardFragment.graphql";
|
||||
import type { LinkedStatementsOfApplicabilityCardFragment$key } from "#/__generated__/core/LinkedStatementsOfApplicabilityCardFragment.graphql";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
import { LinkedStatesOfApplicabilityDialog } from "./LinkedStatesOfApplicabilityDialog";
|
||||
import { LinkedStatementsOfApplicabilityDialog } from "./LinkedStatementsOfApplicabilityDialog";
|
||||
|
||||
const linkedStateOfApplicabilityFragment = graphql`
|
||||
fragment LinkedStatesOfApplicabilityCardFragment on ApplicabilityStatement {
|
||||
const linkedStatementOfApplicabilityFragment = graphql`
|
||||
fragment LinkedStatementsOfApplicabilityCardFragment on ApplicabilityStatement {
|
||||
id
|
||||
stateOfApplicability {
|
||||
statementOfApplicability {
|
||||
id
|
||||
name
|
||||
}
|
||||
@@ -57,7 +57,7 @@ const linkedStateOfApplicabilityFragment = graphql`
|
||||
type AttachMutation<Params> = (p: {
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
applicability: boolean;
|
||||
justification: string | null;
|
||||
} & Params;
|
||||
@@ -68,7 +68,7 @@ type AttachMutation<Params> = (p: {
|
||||
type DetachMutation = (p: {
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
controlId: string;
|
||||
};
|
||||
connections: string[];
|
||||
@@ -76,7 +76,7 @@ type DetachMutation = (p: {
|
||||
}) => void;
|
||||
|
||||
type Props<Params> = {
|
||||
statesOfApplicability: readonly (LinkedStatesOfApplicabilityCardFragment$key & {
|
||||
statementsOfApplicability: readonly (LinkedStatementsOfApplicabilityCardFragment$key & {
|
||||
id: string;
|
||||
})[];
|
||||
params: Params;
|
||||
@@ -88,7 +88,7 @@ type Props<Params> = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
export function LinkedStatementsOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const [limit, setLimit] = useState<number | null>(
|
||||
@@ -96,30 +96,30 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
);
|
||||
|
||||
const [linkedInfo, setLinkedInfo] = useState<
|
||||
{ stateOfApplicabilityId: string; controlId: string }[]
|
||||
{ statementOfApplicabilityId: string; controlId: string }[]
|
||||
>([]);
|
||||
|
||||
const statesOfApplicability = useMemo(() => {
|
||||
const statementsOfApplicability = useMemo(() => {
|
||||
return limit
|
||||
? props.statesOfApplicability.slice(0, limit)
|
||||
: props.statesOfApplicability;
|
||||
}, [props.statesOfApplicability, limit]);
|
||||
? props.statementsOfApplicability.slice(0, limit)
|
||||
: props.statementsOfApplicability;
|
||||
}, [props.statementsOfApplicability, limit]);
|
||||
|
||||
const showMoreButton
|
||||
= limit !== null && props.statesOfApplicability.length > limit;
|
||||
= limit !== null && props.statementsOfApplicability.length > limit;
|
||||
const variant = props.variant ?? "table";
|
||||
|
||||
const linkedData = linkedInfo;
|
||||
|
||||
const onAttach = (
|
||||
stateOfApplicabilityId: string,
|
||||
statementOfApplicabilityId: string,
|
||||
applicability: boolean,
|
||||
justification: string | null,
|
||||
) => {
|
||||
props.onAttach({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
applicability,
|
||||
justification,
|
||||
...props.params,
|
||||
@@ -129,11 +129,11 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
});
|
||||
};
|
||||
|
||||
const onDetach = (stateOfApplicabilityId: string, controlId: string) => {
|
||||
const onDetach = (statementOfApplicabilityId: string, controlId: string) => {
|
||||
props.onDetach({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
controlId,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
@@ -145,7 +145,7 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
|
||||
return (
|
||||
<Wrapper padded className="space-y-[10px]">
|
||||
{props.statesOfApplicability.map((soa, idx) => (
|
||||
{props.statementsOfApplicability.map((soa, idx) => (
|
||||
<LinkedInfoExtractor
|
||||
key={idx}
|
||||
fragment={soa}
|
||||
@@ -153,8 +153,8 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
setLinkedInfo((prev) => {
|
||||
const exists = prev.some(
|
||||
p =>
|
||||
p.stateOfApplicabilityId
|
||||
=== info.stateOfApplicabilityId
|
||||
p.statementOfApplicabilityId
|
||||
=== info.statementOfApplicabilityId
|
||||
&& p.controlId === info.controlId,
|
||||
);
|
||||
return exists ? prev : [...prev, info];
|
||||
@@ -165,20 +165,20 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
{variant === "card" && (
|
||||
<div className="flex justify-between">
|
||||
<div className="text-lg font-semibold">
|
||||
{__("States of Applicability")}
|
||||
{__("Statements of Applicability")}
|
||||
</div>
|
||||
{!props.readOnly && (
|
||||
<LinkedStatesOfApplicabilityDialog
|
||||
<LinkedStatementsOfApplicabilityDialog
|
||||
connectionId={props.connectionId}
|
||||
disabled={props.disabled}
|
||||
linkedStatesOfApplicability={linkedData}
|
||||
linkedStatementsOfApplicability={linkedData}
|
||||
onLink={onAttach}
|
||||
onUnlink={onDetach}
|
||||
>
|
||||
<Button variant="tertiary" icon={IconPlusLarge}>
|
||||
{__("Link state of applicability")}
|
||||
{__("Link statement of applicability")}
|
||||
</Button>
|
||||
</LinkedStatesOfApplicabilityDialog>
|
||||
</LinkedStatementsOfApplicabilityDialog>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -192,36 +192,36 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{statesOfApplicability.length === 0 && (
|
||||
{statementsOfApplicability.length === 0 && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={props.readOnly ? 3 : 4}
|
||||
className="text-center text-txt-secondary"
|
||||
>
|
||||
{__("No states of applicability linked")}
|
||||
{__("No statements of applicability linked")}
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{statesOfApplicability.map(soa => (
|
||||
<StateOfApplicabilityRow
|
||||
{statementsOfApplicability.map(soa => (
|
||||
<StatementOfApplicabilityRow
|
||||
key={soa.id}
|
||||
stateOfApplicability={soa}
|
||||
statementOfApplicability={soa}
|
||||
onClick={onDetach}
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
))}
|
||||
{variant === "table" && !props.readOnly && (
|
||||
<LinkedStatesOfApplicabilityDialog
|
||||
<LinkedStatementsOfApplicabilityDialog
|
||||
connectionId={props.connectionId}
|
||||
disabled={props.disabled}
|
||||
linkedStatesOfApplicability={linkedData}
|
||||
linkedStatementsOfApplicability={linkedData}
|
||||
onLink={onAttach}
|
||||
onUnlink={onDetach}
|
||||
>
|
||||
<TrButton colspan={4} icon={IconPlusLarge}>
|
||||
{__("Link state of applicability")}
|
||||
{__("Link statement of applicability")}
|
||||
</TrButton>
|
||||
</LinkedStatesOfApplicabilityDialog>
|
||||
</LinkedStatementsOfApplicabilityDialog>
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
@@ -233,7 +233,7 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
>
|
||||
{sprintf(
|
||||
__("Show %d more"),
|
||||
props.statesOfApplicability.length - limit,
|
||||
props.statementsOfApplicability.length - limit,
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
@@ -242,48 +242,48 @@ export function LinkedStatesOfApplicabilityCard<Params>(props: Props<Params>) {
|
||||
}
|
||||
|
||||
function LinkedInfoExtractor(props: {
|
||||
fragment: LinkedStatesOfApplicabilityCardFragment$key;
|
||||
fragment: LinkedStatementsOfApplicabilityCardFragment$key;
|
||||
onExtracted: (info: {
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
controlId: string;
|
||||
}) => void;
|
||||
}) {
|
||||
const { onExtracted, fragment } = props;
|
||||
|
||||
const data = useFragment(
|
||||
linkedStateOfApplicabilityFragment,
|
||||
linkedStatementOfApplicabilityFragment,
|
||||
fragment,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onExtracted({
|
||||
stateOfApplicabilityId: data.stateOfApplicability.id,
|
||||
statementOfApplicabilityId: data.statementOfApplicability.id,
|
||||
controlId: data.control.id,
|
||||
});
|
||||
}, [data.stateOfApplicability.id, data.control.id, onExtracted]);
|
||||
}, [data.statementOfApplicability.id, data.control.id, onExtracted]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function StateOfApplicabilityRow(props: {
|
||||
stateOfApplicability: LinkedStatesOfApplicabilityCardFragment$key & {
|
||||
function StatementOfApplicabilityRow(props: {
|
||||
statementOfApplicability: LinkedStatementsOfApplicabilityCardFragment$key & {
|
||||
id: string;
|
||||
};
|
||||
onClick: (stateOfApplicabilityId: string, controlId: string) => void;
|
||||
onClick: (statementOfApplicabilityId: string, controlId: string) => void;
|
||||
readOnly?: boolean;
|
||||
}) {
|
||||
const soa = useFragment(
|
||||
linkedStateOfApplicabilityFragment,
|
||||
props.stateOfApplicability,
|
||||
linkedStatementOfApplicabilityFragment,
|
||||
props.statementOfApplicability,
|
||||
);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Tr
|
||||
to={`/organizations/${organizationId}/states-of-applicability/${soa.stateOfApplicability.id}`}
|
||||
to={`/organizations/${organizationId}/statements-of-applicability/${soa.statementOfApplicability.id}`}
|
||||
>
|
||||
<Td>{soa.stateOfApplicability.name}</Td>
|
||||
<Td>{soa.statementOfApplicability.name}</Td>
|
||||
<Td>
|
||||
<Badge variant={soa.applicability ? "success" : "danger"}>
|
||||
{soa.applicability
|
||||
@@ -298,7 +298,7 @@ function StateOfApplicabilityRow(props: {
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
props.onClick(
|
||||
soa.stateOfApplicability.id,
|
||||
soa.statementOfApplicability.id,
|
||||
soa.control.id,
|
||||
)}
|
||||
icon={IconTrashCan}
|
||||
@@ -27,14 +27,14 @@ import { Suspense, useRef, useState } from "react";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
import type { LinkedStatesOfApplicabilityDialogQuery } from "#/__generated__/core/LinkedStatesOfApplicabilityDialogQuery.graphql";
|
||||
import type { LinkedStatementsOfApplicabilityDialogQuery } from "#/__generated__/core/LinkedStatementsOfApplicabilityDialogQuery.graphql";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
const query = graphql`
|
||||
query LinkedStatesOfApplicabilityDialogQuery($organizationId: ID!) {
|
||||
query LinkedStatementsOfApplicabilityDialogQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
... on Organization {
|
||||
statesOfApplicability(first: 100) {
|
||||
statementsOfApplicability(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
@@ -48,7 +48,7 @@ const query = graphql`
|
||||
`;
|
||||
|
||||
type LinkedSOAInfo = {
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
controlId: string;
|
||||
};
|
||||
|
||||
@@ -56,16 +56,16 @@ type Props = {
|
||||
children: ReactNode;
|
||||
connectionId: string;
|
||||
disabled?: boolean;
|
||||
linkedStatesOfApplicability: readonly LinkedSOAInfo[];
|
||||
linkedStatementsOfApplicability: readonly LinkedSOAInfo[];
|
||||
onLink: (
|
||||
stateOfApplicabilityId: string,
|
||||
statementOfApplicabilityId: string,
|
||||
applicability: boolean,
|
||||
justification: string | null,
|
||||
) => void;
|
||||
onUnlink: (stateOfApplicabilityId: string, controlId: string) => void;
|
||||
onUnlink: (statementOfApplicabilityId: string, controlId: string) => void;
|
||||
};
|
||||
|
||||
export function LinkedStatesOfApplicabilityDialog({
|
||||
export function LinkedStatementsOfApplicabilityDialog({
|
||||
children,
|
||||
...props
|
||||
}: Props) {
|
||||
@@ -75,10 +75,10 @@ export function LinkedStatesOfApplicabilityDialog({
|
||||
<Dialog
|
||||
ref={dialogRef}
|
||||
trigger={children}
|
||||
title="Link State of Applicability"
|
||||
title="Link Statement of Applicability"
|
||||
>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<LinkedStatesOfApplicabilityDialogContent
|
||||
<LinkedStatementsOfApplicabilityDialogContent
|
||||
{...props}
|
||||
onClose={() => dialogRef.current?.close()}
|
||||
/>
|
||||
@@ -87,7 +87,7 @@ export function LinkedStatesOfApplicabilityDialog({
|
||||
);
|
||||
}
|
||||
|
||||
function LinkedStatesOfApplicabilityDialogContent(
|
||||
function LinkedStatementsOfApplicabilityDialogContent(
|
||||
props: Omit<Props, "children"> & { onClose: () => void },
|
||||
) {
|
||||
const { __ } = useTranslate();
|
||||
@@ -99,7 +99,7 @@ function LinkedStatesOfApplicabilityDialogContent(
|
||||
const [applicability, setApplicability] = useState(true);
|
||||
const [justification, setJustification] = useState("");
|
||||
|
||||
const data = useLazyLoadQuery<LinkedStatesOfApplicabilityDialogQuery>(
|
||||
const data = useLazyLoadQuery<LinkedStatementsOfApplicabilityDialogQuery>(
|
||||
query,
|
||||
{
|
||||
organizationId,
|
||||
@@ -108,18 +108,18 @@ function LinkedStatesOfApplicabilityDialogContent(
|
||||
);
|
||||
|
||||
const linkedSOAIds = new Set(
|
||||
props.linkedStatesOfApplicability.map(
|
||||
soa => soa.stateOfApplicabilityId,
|
||||
props.linkedStatementsOfApplicability.map(
|
||||
soa => soa.statementOfApplicabilityId,
|
||||
),
|
||||
);
|
||||
const linkedSOAMap = new Map(
|
||||
props.linkedStatesOfApplicability.map(soa => [
|
||||
soa.stateOfApplicabilityId,
|
||||
props.linkedStatementsOfApplicability.map(soa => [
|
||||
soa.statementOfApplicabilityId,
|
||||
soa,
|
||||
]),
|
||||
);
|
||||
const statesOfApplicability
|
||||
= data.organization?.statesOfApplicability?.edges.map(
|
||||
const statementsOfApplicability
|
||||
= data.organization?.statementsOfApplicability?.edges.map(
|
||||
edge => edge.node,
|
||||
) ?? [];
|
||||
|
||||
@@ -140,11 +140,11 @@ function LinkedStatesOfApplicabilityDialogContent(
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnlink = (stateOfApplicabilityId: string) => {
|
||||
const linkedSOA = linkedSOAMap.get(stateOfApplicabilityId);
|
||||
const handleUnlink = (statementOfApplicabilityId: string) => {
|
||||
const linkedSOA = linkedSOAMap.get(statementOfApplicabilityId);
|
||||
if (linkedSOA) {
|
||||
props.onUnlink(
|
||||
linkedSOA.stateOfApplicabilityId,
|
||||
linkedSOA.statementOfApplicabilityId,
|
||||
linkedSOA.controlId,
|
||||
);
|
||||
}
|
||||
@@ -153,15 +153,15 @@ function LinkedStatesOfApplicabilityDialogContent(
|
||||
return (
|
||||
<>
|
||||
<DialogContent padded className="space-y-4">
|
||||
{statesOfApplicability.length === 0
|
||||
{statementsOfApplicability.length === 0
|
||||
? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="text-txt-secondary text-base mb-2">
|
||||
{__("No states of applicability available")}
|
||||
{__("No statements of applicability available")}
|
||||
</div>
|
||||
<div className="text-txt-tertiary text-sm">
|
||||
{__(
|
||||
"Create a state of applicability first to link it to this control",
|
||||
"Create a statement of applicability first to link it to this control",
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,9 +170,9 @@ function LinkedStatesOfApplicabilityDialogContent(
|
||||
? (
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium mb-2">
|
||||
{__("Select a state of applicability:")}
|
||||
{__("Select a statement of applicability:")}
|
||||
</div>
|
||||
{statesOfApplicability.map((soa) => {
|
||||
{statementsOfApplicability.map((soa) => {
|
||||
const isLinked = linkedSOAIds.has(soa.id);
|
||||
return (
|
||||
<div
|
||||
@@ -64,8 +64,8 @@ const fragment = graphql`
|
||||
canListSnapshots: permission(action: "core:snapshot:list")
|
||||
canGetTrustCenter: permission(action: "core:trust-center:get")
|
||||
canUpdateOrganization: permission(action: "iam:organization:update")
|
||||
canListStatesOfApplicability: permission(
|
||||
action: "core:state-of-applicability:list"
|
||||
canListStatementsOfApplicability: permission(
|
||||
action: "core:statement-of-applicability:list"
|
||||
)
|
||||
canListAccessReviewCampaigns: permission(
|
||||
action: "core:access-review-campaign:list"
|
||||
@@ -183,11 +183,11 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) {
|
||||
to={`${prefix}/processing-activities`}
|
||||
/>
|
||||
)}
|
||||
{organization.canListStatesOfApplicability && (
|
||||
{organization.canListStatementsOfApplicability && (
|
||||
<SidebarItem
|
||||
label={__("States of Applicability")}
|
||||
label={__("Statements of Applicability")}
|
||||
icon={IconPageCheck}
|
||||
to={`${prefix}/states-of-applicability`}
|
||||
to={`${prefix}/statements-of-applicability`}
|
||||
/>
|
||||
)}
|
||||
{organization.canListRightsRequests && (
|
||||
|
||||
@@ -42,56 +42,56 @@ import {
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { StateOfApplicabilityDetailPageDeleteMutation } from "#/__generated__/core/StateOfApplicabilityDetailPageDeleteMutation.graphql";
|
||||
import type { StateOfApplicabilityDetailPageExportMutation } from "#/__generated__/core/StateOfApplicabilityDetailPageExportMutation.graphql";
|
||||
import type { StateOfApplicabilityDetailPageQuery } from "#/__generated__/core/StateOfApplicabilityDetailPageQuery.graphql";
|
||||
import type { StateOfApplicabilityDetailPageUpdateMutation } from "#/__generated__/core/StateOfApplicabilityDetailPageUpdateMutation.graphql";
|
||||
import type { StatementOfApplicabilityDetailPageDeleteMutation } from "#/__generated__/core/StatementOfApplicabilityDetailPageDeleteMutation.graphql";
|
||||
import type { StatementOfApplicabilityDetailPageExportMutation } from "#/__generated__/core/StatementOfApplicabilityDetailPageExportMutation.graphql";
|
||||
import type { StatementOfApplicabilityDetailPageQuery } from "#/__generated__/core/StatementOfApplicabilityDetailPageQuery.graphql";
|
||||
import type { StatementOfApplicabilityDetailPageUpdateMutation } from "#/__generated__/core/StatementOfApplicabilityDetailPageUpdateMutation.graphql";
|
||||
import { PeopleSelectField } from "#/components/form/PeopleSelectField";
|
||||
import { SnapshotBanner } from "#/components/SnapshotBanner";
|
||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
import StateOfApplicabilityControlsTab from "./tabs/StateOfApplicabilityControlsTab";
|
||||
import StatementOfApplicabilityControlsTab from "./tabs/StatementOfApplicabilityControlsTab";
|
||||
|
||||
export const stateOfApplicabilityDetailPageQuery = graphql`
|
||||
query StateOfApplicabilityDetailPageQuery($stateOfApplicabilityId: ID!) {
|
||||
node(id: $stateOfApplicabilityId) {
|
||||
... on StateOfApplicability {
|
||||
export const statementOfApplicabilityDetailPageQuery = graphql`
|
||||
query StatementOfApplicabilityDetailPageQuery($statementOfApplicabilityId: ID!) {
|
||||
node(id: $statementOfApplicabilityId) {
|
||||
... on StatementOfApplicability {
|
||||
id
|
||||
name
|
||||
snapshotId
|
||||
createdAt
|
||||
updatedAt
|
||||
canUpdate: permission(action: "core:state-of-applicability:update")
|
||||
canDelete: permission(action: "core:state-of-applicability:delete")
|
||||
canExport: permission(action: "core:state-of-applicability:export")
|
||||
canUpdate: permission(action: "core:statement-of-applicability:update")
|
||||
canDelete: permission(action: "core:statement-of-applicability:delete")
|
||||
canExport: permission(action: "core:statement-of-applicability:export")
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
...StateOfApplicabilityControlsTabFragment
|
||||
...StatementOfApplicabilityControlsTabFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const exportMutation = graphql`
|
||||
mutation StateOfApplicabilityDetailPageExportMutation(
|
||||
$input: ExportStateOfApplicabilityPDFInput!
|
||||
mutation StatementOfApplicabilityDetailPageExportMutation(
|
||||
$input: ExportStatementOfApplicabilityPDFInput!
|
||||
) {
|
||||
exportStateOfApplicabilityPDF(input: $input) {
|
||||
exportStatementOfApplicabilityPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const updateMutation = graphql`
|
||||
mutation StateOfApplicabilityDetailPageUpdateMutation(
|
||||
$input: UpdateStateOfApplicabilityInput!
|
||||
mutation StatementOfApplicabilityDetailPageUpdateMutation(
|
||||
$input: UpdateStatementOfApplicabilityInput!
|
||||
) {
|
||||
updateStateOfApplicability(input: $input) {
|
||||
stateOfApplicability {
|
||||
updateStatementOfApplicability(input: $input) {
|
||||
statementOfApplicability {
|
||||
id
|
||||
name
|
||||
sourceId
|
||||
@@ -108,74 +108,75 @@ const updateMutation = graphql`
|
||||
`;
|
||||
|
||||
const deleteMutation = graphql`
|
||||
mutation StateOfApplicabilityDetailPageDeleteMutation(
|
||||
$input: DeleteStateOfApplicabilityInput!
|
||||
mutation StatementOfApplicabilityDetailPageDeleteMutation(
|
||||
$input: DeleteStatementOfApplicabilityInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteStateOfApplicability(input: $input) {
|
||||
deletedStateOfApplicabilityId @deleteEdge(connections: $connections)
|
||||
deleteStatementOfApplicability(input: $input) {
|
||||
deletedStatementOfApplicabilityId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StateOfApplicabilityConnectionKey = "StatesOfApplicabilityPage_statesOfApplicability";
|
||||
const StatementOfApplicabilityConnectionKey = "StatementsOfApplicabilityPage_statementsOfApplicability";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<StateOfApplicabilityDetailPageQuery>;
|
||||
queryRef: PreloadedQuery<StatementOfApplicabilityDetailPageQuery>;
|
||||
};
|
||||
|
||||
export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
const { stateOfApplicabilityId, snapshotId } = useParams<{
|
||||
stateOfApplicabilityId: string;
|
||||
export default function StatementOfApplicabilityDetailPage(props: Props) {
|
||||
const { statementOfApplicabilityId, snapshotId } = useParams<{
|
||||
statementOfApplicabilityId: string;
|
||||
snapshotId?: string;
|
||||
}>();
|
||||
const organizationId = useOrganizationId();
|
||||
const data = usePreloadedQuery(stateOfApplicabilityDetailPageQuery, props.queryRef);
|
||||
const stateOfApplicability = data.node;
|
||||
const data = usePreloadedQuery(statementOfApplicabilityDetailPageQuery, props.queryRef);
|
||||
const statementOfApplicability = data.node;
|
||||
const { __ } = useTranslate();
|
||||
const navigate = useNavigate();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
const confirm = useConfirm();
|
||||
const { toast } = useToast();
|
||||
|
||||
if (!stateOfApplicabilityId || !stateOfApplicability) {
|
||||
if (!statementOfApplicabilityId || !statementOfApplicability) {
|
||||
throw new Error(
|
||||
"Cannot load state of applicability detail page without stateOfApplicabilityId parameter",
|
||||
"Cannot load statement of applicability detail page without statementOfApplicabilityId parameter",
|
||||
);
|
||||
}
|
||||
|
||||
validateSnapshotConsistency(stateOfApplicability, snapshotId);
|
||||
validateSnapshotConsistency(statementOfApplicability, snapshotId);
|
||||
|
||||
const connectionId = ConnectionHandler.getConnectionID(
|
||||
organizationId,
|
||||
StateOfApplicabilityConnectionKey,
|
||||
StatementOfApplicabilityConnectionKey,
|
||||
{ filter: { snapshotId: snapshotId ?? null } },
|
||||
);
|
||||
|
||||
const [deleteStateOfApplicability] = useMutation<StateOfApplicabilityDetailPageDeleteMutation>(deleteMutation);
|
||||
const [deleteStatementOfApplicability]
|
||||
= useMutation<StatementOfApplicabilityDetailPageDeleteMutation>(deleteMutation);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!stateOfApplicability.id || !stateOfApplicability.name) {
|
||||
return alert(__("Failed to delete state of applicability: missing id or name"));
|
||||
if (!statementOfApplicability.id || !statementOfApplicability.name) {
|
||||
return alert(__("Failed to delete statement of applicability: missing id or name"));
|
||||
}
|
||||
confirm(
|
||||
() =>
|
||||
promisifyMutation(deleteStateOfApplicability)({
|
||||
promisifyMutation(deleteStatementOfApplicability)({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId: stateOfApplicability.id!,
|
||||
statementOfApplicabilityId: statementOfApplicability.id!,
|
||||
},
|
||||
connections: [connectionId],
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
void navigate(`/organizations/${organizationId}/states-of-applicability`);
|
||||
void navigate(`/organizations/${organizationId}/statements-of-applicability`);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: formatError(
|
||||
__("Failed to delete state of applicability"),
|
||||
__("Failed to delete statement of applicability"),
|
||||
error as GraphQLError,
|
||||
),
|
||||
variant: "error",
|
||||
@@ -186,52 +187,53 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
__(
|
||||
"This will permanently delete \"%s\". This action cannot be undone.",
|
||||
),
|
||||
stateOfApplicability.name,
|
||||
statementOfApplicability.name,
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
usePageTitle(stateOfApplicability.name || __("State of Applicability"));
|
||||
usePageTitle(statementOfApplicability.name || __("Statement of Applicability"));
|
||||
|
||||
const [isEditingName, setIsEditingName] = useState(false);
|
||||
const [isEditingOwner, setIsEditingOwner] = useState(false);
|
||||
const [updateStateOfApplicability, isUpdating] = useMutationWithToasts<StateOfApplicabilityDetailPageUpdateMutation>(
|
||||
updateMutation,
|
||||
{
|
||||
successMessage: __("State of Applicability updated successfully."),
|
||||
errorMessage: __("Failed to update State of Applicability"),
|
||||
},
|
||||
);
|
||||
const [updateStatementOfApplicability, isUpdating]
|
||||
= useMutationWithToasts<StatementOfApplicabilityDetailPageUpdateMutation>(
|
||||
updateMutation,
|
||||
{
|
||||
successMessage: __("Statement of Applicability updated successfully."),
|
||||
errorMessage: __("Failed to update Statement of Applicability"),
|
||||
},
|
||||
);
|
||||
|
||||
const canUpdate = !isSnapshotMode && stateOfApplicability.canUpdate;
|
||||
const canDelete = !isSnapshotMode && stateOfApplicability.canDelete;
|
||||
const canUpdate = !isSnapshotMode && statementOfApplicability.canUpdate;
|
||||
const canDelete = !isSnapshotMode && statementOfApplicability.canDelete;
|
||||
|
||||
const [exportStateOfApplicabilityPDF, isExporting]
|
||||
= useMutationWithToasts<StateOfApplicabilityDetailPageExportMutation>(
|
||||
const [exportStatementOfApplicabilityPDF, isExporting]
|
||||
= useMutationWithToasts<StatementOfApplicabilityDetailPageExportMutation>(
|
||||
exportMutation,
|
||||
{
|
||||
successMessage: __(
|
||||
"State of Applicability exported successfully.",
|
||||
"Statement of Applicability exported successfully.",
|
||||
),
|
||||
errorMessage: __("Failed to export State of Applicability"),
|
||||
errorMessage: __("Failed to export Statement of Applicability"),
|
||||
},
|
||||
);
|
||||
|
||||
const handleExport = async () => {
|
||||
if (!stateOfApplicability.id) return;
|
||||
if (!statementOfApplicability.id) return;
|
||||
|
||||
await exportStateOfApplicabilityPDF({
|
||||
await exportStatementOfApplicabilityPDF({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId: stateOfApplicability.id,
|
||||
statementOfApplicabilityId: statementOfApplicability.id,
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data.exportStateOfApplicabilityPDF?.data) {
|
||||
if (data.exportStatementOfApplicabilityPDF?.data) {
|
||||
const link = window.document.createElement("a");
|
||||
link.href = data.exportStateOfApplicabilityPDF.data;
|
||||
link.download = `${stateOfApplicability.name || "state-of-applicability"}.pdf`;
|
||||
link.href = data.exportStatementOfApplicabilityPDF.data;
|
||||
link.download = `${statementOfApplicability.name || "statement-of-applicability"}.pdf`;
|
||||
window.document.body.appendChild(link);
|
||||
link.click();
|
||||
window.document.body.removeChild(link);
|
||||
@@ -254,7 +256,7 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
reset: resetName,
|
||||
} = useFormWithSchema(nameSchema, {
|
||||
defaultValues: {
|
||||
name: stateOfApplicability.name || "",
|
||||
name: statementOfApplicability.name || "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -264,17 +266,17 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
reset: resetOwner,
|
||||
} = useFormWithSchema(ownerSchema, {
|
||||
defaultValues: {
|
||||
ownerId: stateOfApplicability.owner?.id || "",
|
||||
ownerId: statementOfApplicability.owner?.id || "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleUpdateName = handleSubmitName(async (data) => {
|
||||
if (!stateOfApplicability.id) return;
|
||||
if (!statementOfApplicability.id) return;
|
||||
|
||||
await updateStateOfApplicability({
|
||||
await updateStatementOfApplicability({
|
||||
variables: {
|
||||
input: {
|
||||
id: stateOfApplicability.id,
|
||||
id: statementOfApplicability.id,
|
||||
name: data.name,
|
||||
},
|
||||
},
|
||||
@@ -286,12 +288,12 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
});
|
||||
|
||||
const handleUpdateOwner = handleSubmitOwner(async (data) => {
|
||||
if (!stateOfApplicability.id) return;
|
||||
if (!statementOfApplicability.id) return;
|
||||
|
||||
await updateStateOfApplicability({
|
||||
await updateStatementOfApplicability({
|
||||
variables: {
|
||||
input: {
|
||||
id: stateOfApplicability.id,
|
||||
id: statementOfApplicability.id,
|
||||
ownerId: data.ownerId,
|
||||
},
|
||||
},
|
||||
@@ -305,20 +307,20 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
const handleCancelNameEdit = () => {
|
||||
setIsEditingName(false);
|
||||
resetName({
|
||||
name: stateOfApplicability.name || "",
|
||||
name: statementOfApplicability.name || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelOwnerEdit = () => {
|
||||
setIsEditingOwner(false);
|
||||
resetOwner({
|
||||
ownerId: stateOfApplicability.owner?.id || "",
|
||||
ownerId: statementOfApplicability.owner?.id || "",
|
||||
});
|
||||
};
|
||||
|
||||
const listUrl = snapshotId
|
||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/states-of-applicability`
|
||||
: `/organizations/${organizationId}/states-of-applicability`;
|
||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/statements-of-applicability`
|
||||
: `/organizations/${organizationId}/statements-of-applicability`;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -326,13 +328,13 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
label: __("States of Applicability"),
|
||||
label: __("Statements of Applicability"),
|
||||
to: listUrl,
|
||||
},
|
||||
{
|
||||
label:
|
||||
stateOfApplicability.name
|
||||
|| __("State of Applicability detail"),
|
||||
statementOfApplicability.name
|
||||
|| __("Statement of Applicability detail"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -371,7 +373,7 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
)
|
||||
: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{stateOfApplicability.name || ""}</span>
|
||||
<span>{statementOfApplicability.name || ""}</span>
|
||||
{canUpdate && (
|
||||
<Button
|
||||
variant="quaternary"
|
||||
@@ -383,7 +385,7 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
)
|
||||
}
|
||||
>
|
||||
{stateOfApplicability.canExport && (
|
||||
{statementOfApplicability.canExport && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconArrowDown}
|
||||
@@ -445,7 +447,7 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
: (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-sm text-txt-primary">
|
||||
{stateOfApplicability.owner
|
||||
{statementOfApplicability.owner
|
||||
?.fullName || "-"}
|
||||
</div>
|
||||
{canUpdate && (
|
||||
@@ -466,7 +468,7 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
{__("Created at")}
|
||||
</div>
|
||||
<div className="text-sm text-txt-primary">
|
||||
{formatDate(stateOfApplicability.createdAt)}
|
||||
{formatDate(statementOfApplicability.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -474,21 +476,21 @@ export default function StateOfApplicabilityDetailPage(props: Props) {
|
||||
{__("Updated at")}
|
||||
</div>
|
||||
<div className="text-sm text-txt-primary">
|
||||
{formatDate(stateOfApplicability.updatedAt)}
|
||||
{formatDate(statementOfApplicability.updatedAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{stateOfApplicability.id && (
|
||||
{statementOfApplicability.id && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-base font-medium">
|
||||
{__("Statements")}
|
||||
</h2>
|
||||
<StateOfApplicabilityControlsTab
|
||||
stateOfApplicability={
|
||||
stateOfApplicability as typeof stateOfApplicability & {
|
||||
<StatementOfApplicabilityControlsTab
|
||||
statementOfApplicability={
|
||||
statementOfApplicability as typeof statementOfApplicability & {
|
||||
id: string;
|
||||
}
|
||||
}
|
||||
@@ -16,21 +16,21 @@ import { Suspense, useEffect } from "react";
|
||||
import { useQueryLoader } from "react-relay";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
import type { StateOfApplicabilityDetailPageQuery } from "#/__generated__/core/StateOfApplicabilityDetailPageQuery.graphql";
|
||||
import type { StatementOfApplicabilityDetailPageQuery } from "#/__generated__/core/StatementOfApplicabilityDetailPageQuery.graphql";
|
||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||
|
||||
import StateOfApplicabilityDetailPage, { stateOfApplicabilityDetailPageQuery } from "./StateOfApplicabilityDetailPage";
|
||||
import StatementOfApplicabilityDetailPage, { statementOfApplicabilityDetailPageQuery } from "./StatementOfApplicabilityDetailPage";
|
||||
|
||||
export default function StateOfApplicabilityDetailPageLoader() {
|
||||
const { stateOfApplicabilityId } = useParams<{ stateOfApplicabilityId: string }>();
|
||||
export default function StatementOfApplicabilityDetailPageLoader() {
|
||||
const { statementOfApplicabilityId } = useParams<{ statementOfApplicabilityId: string }>();
|
||||
const [queryRef, loadQuery]
|
||||
= useQueryLoader<StateOfApplicabilityDetailPageQuery>(stateOfApplicabilityDetailPageQuery);
|
||||
= useQueryLoader<StatementOfApplicabilityDetailPageQuery>(statementOfApplicabilityDetailPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (stateOfApplicabilityId) {
|
||||
loadQuery({ stateOfApplicabilityId });
|
||||
if (statementOfApplicabilityId) {
|
||||
loadQuery({ statementOfApplicabilityId });
|
||||
}
|
||||
}, [loadQuery, stateOfApplicabilityId]);
|
||||
}, [loadQuery, statementOfApplicabilityId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <PageSkeleton />;
|
||||
@@ -38,7 +38,7 @@ export default function StateOfApplicabilityDetailPageLoader() {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<PageSkeleton />}>
|
||||
<StateOfApplicabilityDetailPage queryRef={queryRef} />
|
||||
<StatementOfApplicabilityDetailPage queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -34,86 +34,86 @@ import {
|
||||
} from "react-relay";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
import type { StatesOfApplicabilityPageFragment$key } from "#/__generated__/core/StatesOfApplicabilityPageFragment.graphql";
|
||||
import type { StatesOfApplicabilityPagePaginationQuery } from "#/__generated__/core/StatesOfApplicabilityPagePaginationQuery.graphql";
|
||||
import type { StatesOfApplicabilityPageQuery } from "#/__generated__/core/StatesOfApplicabilityPageQuery.graphql";
|
||||
import type { StatementsOfApplicabilityPageFragment$key } from "#/__generated__/core/StatementsOfApplicabilityPageFragment.graphql";
|
||||
import type { StatementsOfApplicabilityPagePaginationQuery } from "#/__generated__/core/StatementsOfApplicabilityPagePaginationQuery.graphql";
|
||||
import type { StatementsOfApplicabilityPageQuery } from "#/__generated__/core/StatementsOfApplicabilityPageQuery.graphql";
|
||||
import { SnapshotBanner } from "#/components/SnapshotBanner";
|
||||
|
||||
import { StateOfApplicabilityRow } from "./_components/StateOfApplicabilityRow";
|
||||
import { CreateStateOfApplicabilityDialog } from "./dialogs/CreateStateOfApplicabilityDialog";
|
||||
import { StatementOfApplicabilityRow } from "./_components/StatementOfApplicabilityRow";
|
||||
import { CreateStatementOfApplicabilityDialog } from "./dialogs/CreateStatementOfApplicabilityDialog";
|
||||
|
||||
export const statesOfApplicabilityPageQuery = graphql`
|
||||
query StatesOfApplicabilityPageQuery($organizationId: ID!) {
|
||||
export const statementsOfApplicabilityPageQuery = graphql`
|
||||
query StatementsOfApplicabilityPageQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
__typename
|
||||
... on Organization {
|
||||
id
|
||||
canCreateStateOfApplicability: permission(action: "core:state-of-applicability:create")
|
||||
...StatesOfApplicabilityPageFragment
|
||||
canCreateStatementOfApplicability: permission(action: "core:statement-of-applicability:create")
|
||||
...StatementsOfApplicabilityPageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const paginatedFragment = graphql`
|
||||
fragment StatesOfApplicabilityPageFragment on Organization
|
||||
@refetchable(queryName: "StatesOfApplicabilityPagePaginationQuery")
|
||||
fragment StatementsOfApplicabilityPageFragment on Organization
|
||||
@refetchable(queryName: "StatementsOfApplicabilityPagePaginationQuery")
|
||||
@argumentDefinitions(
|
||||
first: { type: "Int", defaultValue: 50 }
|
||||
order: {
|
||||
type: "StateOfApplicabilityOrder"
|
||||
type: "StatementOfApplicabilityOrder"
|
||||
defaultValue: { direction: DESC, field: CREATED_AT }
|
||||
}
|
||||
after: { type: "CursorKey", defaultValue: null }
|
||||
before: { type: "CursorKey", defaultValue: null }
|
||||
last: { type: "Int", defaultValue: null }
|
||||
filter: { type: "StateOfApplicabilityFilter", defaultValue: { snapshotId: null } }
|
||||
filter: { type: "StatementOfApplicabilityFilter", defaultValue: { snapshotId: null } }
|
||||
) {
|
||||
statesOfApplicability(
|
||||
statementsOfApplicability(
|
||||
first: $first
|
||||
after: $after
|
||||
last: $last
|
||||
before: $before
|
||||
orderBy: $order
|
||||
filter: $filter
|
||||
) @connection(key: "StatesOfApplicabilityPage_statesOfApplicability", filters: ["filter"]) {
|
||||
) @connection(key: "StatementsOfApplicabilityPage_statementsOfApplicability", filters: ["filter"]) {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...StateOfApplicabilityRowFragment
|
||||
...StatementOfApplicabilityRowFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function StatesOfApplicabilityPage({
|
||||
export default function StatementsOfApplicabilityPage({
|
||||
queryRef,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<StatesOfApplicabilityPageQuery>;
|
||||
queryRef: PreloadedQuery<StatementsOfApplicabilityPageQuery>;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
|
||||
usePageTitle(__("States of Applicability"));
|
||||
usePageTitle(__("Statements of Applicability"));
|
||||
|
||||
const { organization } = usePreloadedQuery(statesOfApplicabilityPageQuery, queryRef);
|
||||
const { organization } = usePreloadedQuery(statementsOfApplicabilityPageQuery, queryRef);
|
||||
|
||||
if (organization.__typename !== "Organization") {
|
||||
throw new Error("Organization not found");
|
||||
}
|
||||
|
||||
const {
|
||||
data: { statesOfApplicability },
|
||||
data: { statementsOfApplicability },
|
||||
loadNext,
|
||||
hasNext,
|
||||
refetch,
|
||||
isLoadingNext,
|
||||
} = usePaginationFragment<
|
||||
StatesOfApplicabilityPagePaginationQuery,
|
||||
StatesOfApplicabilityPageFragment$key
|
||||
StatementsOfApplicabilityPagePaginationQuery,
|
||||
StatementsOfApplicabilityPageFragment$key
|
||||
>(paginatedFragment, organization);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -129,24 +129,24 @@ export default function StatesOfApplicabilityPage({
|
||||
<div className="space-y-6">
|
||||
{snapshotId && <SnapshotBanner snapshotId={snapshotId} />}
|
||||
<PageHeader
|
||||
title={__("States of Applicability")}
|
||||
title={__("Statements of Applicability")}
|
||||
description={__(
|
||||
"Manage states of applicability for your organization's frameworks.",
|
||||
"Manage statements of applicability for your organization's frameworks.",
|
||||
)}
|
||||
>
|
||||
{!isSnapshotMode
|
||||
&& organization.canCreateStateOfApplicability && (
|
||||
<CreateStateOfApplicabilityDialog
|
||||
connectionId={statesOfApplicability.__id}
|
||||
&& organization.canCreateStatementOfApplicability && (
|
||||
<CreateStatementOfApplicabilityDialog
|
||||
connectionId={statementsOfApplicability.__id}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>
|
||||
{__("Add state of applicability")}
|
||||
{__("Add statement of applicability")}
|
||||
</Button>
|
||||
</CreateStateOfApplicabilityDialog>
|
||||
</CreateStatementOfApplicabilityDialog>
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
{statesOfApplicability && statesOfApplicability.edges.length > 0
|
||||
{statementsOfApplicability && statementsOfApplicability.edges.length > 0
|
||||
? (
|
||||
<Card>
|
||||
<Table>
|
||||
@@ -158,11 +158,11 @@ export default function StatesOfApplicabilityPage({
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{statesOfApplicability.edges.map(edge => (
|
||||
<StateOfApplicabilityRow
|
||||
{statementsOfApplicability.edges.map(edge => (
|
||||
<StatementOfApplicabilityRow
|
||||
key={edge.node.id}
|
||||
fKey={edge.node}
|
||||
connectionId={statesOfApplicability.__id}
|
||||
connectionId={statementsOfApplicability.__id}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
@@ -187,11 +187,11 @@ export default function StatesOfApplicabilityPage({
|
||||
<Card padded>
|
||||
<div className="text-center py-12">
|
||||
<h3 className="text-lg font-semibold mb-2">
|
||||
{__("No states of applicability yet")}
|
||||
{__("No statements of applicability yet")}
|
||||
</h3>
|
||||
<p className="text-txt-tertiary mb-4">
|
||||
{__(
|
||||
"Create your first state of applicability to get started.",
|
||||
"Create your first statement of applicability to get started.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -15,16 +15,16 @@
|
||||
import { Suspense, useEffect } from "react";
|
||||
import { useQueryLoader } from "react-relay";
|
||||
|
||||
import type { StatesOfApplicabilityPageQuery } from "#/__generated__/core/StatesOfApplicabilityPageQuery.graphql";
|
||||
import type { StatementsOfApplicabilityPageQuery } from "#/__generated__/core/StatementsOfApplicabilityPageQuery.graphql";
|
||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
import StatesOfApplicabilityPage, { statesOfApplicabilityPageQuery } from "./StatesOfApplicabilityPage";
|
||||
import StatementsOfApplicabilityPage, { statementsOfApplicabilityPageQuery } from "./StatementsOfApplicabilityPage";
|
||||
|
||||
export default function StatesOfApplicabilityPageLoader() {
|
||||
export default function StatementsOfApplicabilityPageLoader() {
|
||||
const organizationId = useOrganizationId();
|
||||
const [queryRef, loadQuery]
|
||||
= useQueryLoader<StatesOfApplicabilityPageQuery>(statesOfApplicabilityPageQuery);
|
||||
= useQueryLoader<StatementsOfApplicabilityPageQuery>(statementsOfApplicabilityPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({ organizationId });
|
||||
@@ -36,7 +36,7 @@ export default function StatesOfApplicabilityPageLoader() {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<PageSkeleton />}>
|
||||
<StatesOfApplicabilityPage queryRef={queryRef} />
|
||||
<StatementsOfApplicabilityPage queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -27,16 +27,16 @@ import { useFragment, useMutation } from "react-relay";
|
||||
import { useParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
import type { StateOfApplicabilityRowDeleteMutation } from "#/__generated__/core/StateOfApplicabilityRowDeleteMutation.graphql";
|
||||
import type { StateOfApplicabilityRowFragment$key } from "#/__generated__/core/StateOfApplicabilityRowFragment.graphql";
|
||||
import type { StatementOfApplicabilityRowDeleteMutation } from "#/__generated__/core/StatementOfApplicabilityRowDeleteMutation.graphql";
|
||||
import type { StatementOfApplicabilityRowFragment$key } from "#/__generated__/core/StatementOfApplicabilityRowFragment.graphql";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
const fragment = graphql`
|
||||
fragment StateOfApplicabilityRowFragment on StateOfApplicability {
|
||||
fragment StatementOfApplicabilityRowFragment on StatementOfApplicability {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
canDelete: permission(action: "core:state-of-applicability:delete")
|
||||
canDelete: permission(action: "core:statement-of-applicability:delete")
|
||||
statementsInfo: applicabilityStatements {
|
||||
totalCount
|
||||
}
|
||||
@@ -44,22 +44,22 @@ const fragment = graphql`
|
||||
`;
|
||||
|
||||
const deleteMutation = graphql`
|
||||
mutation StateOfApplicabilityRowDeleteMutation(
|
||||
$input: DeleteStateOfApplicabilityInput!
|
||||
mutation StatementOfApplicabilityRowDeleteMutation(
|
||||
$input: DeleteStatementOfApplicabilityInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteStateOfApplicability(input: $input) {
|
||||
deletedStateOfApplicabilityId @deleteEdge(connections: $connections)
|
||||
deleteStatementOfApplicability(input: $input) {
|
||||
deletedStatementOfApplicabilityId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
fKey: StateOfApplicabilityRowFragment$key;
|
||||
fKey: StatementOfApplicabilityRowFragment$key;
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export function StateOfApplicabilityRow({ fKey, connectionId }: Props) {
|
||||
export function StatementOfApplicabilityRow({ fKey, connectionId }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const organizationId = useOrganizationId();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
@@ -67,21 +67,21 @@ export function StateOfApplicabilityRow({ fKey, connectionId }: Props) {
|
||||
const { toast } = useToast();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
|
||||
const stateOfApplicability = useFragment(fragment, fKey);
|
||||
const canDelete = !isSnapshotMode && stateOfApplicability.canDelete;
|
||||
const statementOfApplicability = useFragment(fragment, fKey);
|
||||
const canDelete = !isSnapshotMode && statementOfApplicability.canDelete;
|
||||
|
||||
const [deleteStateOfApplicability] = useMutation<StateOfApplicabilityRowDeleteMutation>(deleteMutation);
|
||||
const [deleteStatementOfApplicability] = useMutation<StatementOfApplicabilityRowDeleteMutation>(deleteMutation);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!stateOfApplicability.id || !stateOfApplicability.name) {
|
||||
return alert(__("Failed to delete state of applicability: missing id or name"));
|
||||
if (!statementOfApplicability.id || !statementOfApplicability.name) {
|
||||
return alert(__("Failed to delete statement of applicability: missing id or name"));
|
||||
}
|
||||
confirm(
|
||||
() =>
|
||||
promisifyMutation(deleteStateOfApplicability)({
|
||||
promisifyMutation(deleteStatementOfApplicability)({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId: stateOfApplicability.id,
|
||||
statementOfApplicabilityId: statementOfApplicability.id,
|
||||
},
|
||||
connections: [connectionId],
|
||||
},
|
||||
@@ -89,7 +89,7 @@ export function StateOfApplicabilityRow({ fKey, connectionId }: Props) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: formatError(
|
||||
__("Failed to delete state of applicability"),
|
||||
__("Failed to delete statement of applicability"),
|
||||
error as GraphQLError,
|
||||
),
|
||||
variant: "error",
|
||||
@@ -100,25 +100,25 @@ export function StateOfApplicabilityRow({ fKey, connectionId }: Props) {
|
||||
__(
|
||||
"This will permanently delete \"%s\". This action cannot be undone.",
|
||||
),
|
||||
stateOfApplicability.name,
|
||||
statementOfApplicability.name,
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const detailUrl = snapshotId
|
||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/states-of-applicability/${stateOfApplicability.id}`
|
||||
: `/organizations/${organizationId}/states-of-applicability/${stateOfApplicability.id}`;
|
||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/statements-of-applicability/${statementOfApplicability.id}`
|
||||
: `/organizations/${organizationId}/statements-of-applicability/${statementOfApplicability.id}`;
|
||||
|
||||
return (
|
||||
<Tr to={detailUrl}>
|
||||
<Td>{stateOfApplicability.name}</Td>
|
||||
<Td>{statementOfApplicability.name}</Td>
|
||||
<Td>
|
||||
<time dateTime={stateOfApplicability.createdAt}>
|
||||
{formatDate(stateOfApplicability.createdAt)}
|
||||
<time dateTime={statementOfApplicability.createdAt}>
|
||||
{formatDate(statementOfApplicability.createdAt)}
|
||||
</time>
|
||||
</Td>
|
||||
<Td>{stateOfApplicability.statementsInfo?.totalCount ?? 0}</Td>
|
||||
<Td>{statementOfApplicability.statementsInfo?.totalCount ?? 0}</Td>
|
||||
{canDelete && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
@@ -38,9 +38,9 @@ import type { AddApplicabilityStatementDialogQuery } from "#/__generated__/core/
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
|
||||
const query = graphql`
|
||||
query AddApplicabilityStatementDialogQuery($stateOfApplicabilityId: ID!, $organizationId: ID!) {
|
||||
stateOfApplicability: node(id: $stateOfApplicabilityId) {
|
||||
... on StateOfApplicability {
|
||||
query AddApplicabilityStatementDialogQuery($statementOfApplicabilityId: ID!, $organizationId: ID!) {
|
||||
statementOfApplicability: node(id: $statementOfApplicabilityId) {
|
||||
... on StatementOfApplicability {
|
||||
id
|
||||
applicabilityStatements(first: 10000) {
|
||||
edges {
|
||||
@@ -138,7 +138,7 @@ const updateApplicabilityStatementMutation = graphql`
|
||||
`;
|
||||
|
||||
export type AddApplicabilityStatementDialogRef = {
|
||||
open: (stateOfApplicabilityId: string, organizationId: string, connectionId: string) => void;
|
||||
open: (statementOfApplicabilityId: string, organizationId: string, connectionId: string) => void;
|
||||
};
|
||||
|
||||
type ControlWithStatement = {
|
||||
@@ -154,11 +154,11 @@ type ControlWithStatement = {
|
||||
|
||||
function ControlRow({
|
||||
control,
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
connectionId,
|
||||
}: {
|
||||
control: ControlWithStatement;
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
connectionId: string;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
@@ -228,7 +228,7 @@ function ControlRow({
|
||||
await createApplicabilityStatement({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
controlId: control.controlId,
|
||||
applicability: true,
|
||||
justification: null,
|
||||
@@ -263,7 +263,7 @@ function ControlRow({
|
||||
await createApplicabilityStatement({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
controlId: control.controlId,
|
||||
applicability: false,
|
||||
justification: justification || null,
|
||||
@@ -333,11 +333,11 @@ function ControlRow({
|
||||
}
|
||||
|
||||
function AddApplicabilityStatementDialogContent({
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
organizationId,
|
||||
connectionId,
|
||||
}: {
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
organizationId: string;
|
||||
connectionId: string;
|
||||
}) {
|
||||
@@ -346,7 +346,7 @@ function AddApplicabilityStatementDialogContent({
|
||||
const [collapsedFrameworks, setCollapsedFrameworks] = useState<Set<string>>(new Set());
|
||||
const data = useLazyLoadQuery<AddApplicabilityStatementDialogQuery>(
|
||||
query,
|
||||
{ stateOfApplicabilityId, organizationId },
|
||||
{ statementOfApplicabilityId, organizationId },
|
||||
{ fetchPolicy: "store-or-network" },
|
||||
);
|
||||
|
||||
@@ -355,7 +355,7 @@ function AddApplicabilityStatementDialogContent({
|
||||
string,
|
||||
{ id: string; applicability: boolean; justification: string | null }
|
||||
>();
|
||||
data.stateOfApplicability?.applicabilityStatements?.edges.forEach((edge) => {
|
||||
data.statementOfApplicability?.applicabilityStatements?.edges.forEach((edge) => {
|
||||
map.set(edge.node.control.id, {
|
||||
id: edge.node.id,
|
||||
applicability: edge.node.applicability,
|
||||
@@ -363,7 +363,7 @@ function AddApplicabilityStatementDialogContent({
|
||||
});
|
||||
});
|
||||
return map;
|
||||
}, [data.stateOfApplicability?.applicabilityStatements]);
|
||||
}, [data.statementOfApplicability?.applicabilityStatements]);
|
||||
|
||||
const allControls = useMemo(() => {
|
||||
return (data.organization?.controls?.edges || []).map((edge) => {
|
||||
@@ -459,7 +459,7 @@ function AddApplicabilityStatementDialogContent({
|
||||
<ControlRow
|
||||
key={control.controlId}
|
||||
control={control}
|
||||
stateOfApplicabilityId={stateOfApplicabilityId}
|
||||
statementOfApplicabilityId={statementOfApplicabilityId}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
))}
|
||||
@@ -488,7 +488,7 @@ export const AddApplicabilityStatementDialog = forwardRef<
|
||||
({ onClose }, ref) => {
|
||||
const { __ } = useTranslate();
|
||||
const dialogRef = useDialogRef();
|
||||
const [stateOfApplicabilityId, setStateOfApplicabilityId] = useState<string | null>(null);
|
||||
const [statementOfApplicabilityId, setStatementOfApplicabilityId] = useState<string | null>(null);
|
||||
const [organizationId, setOrganizationId] = useState<string | null>(null);
|
||||
const [connectionId, setConnectionId] = useState<string | null>(null);
|
||||
|
||||
@@ -496,7 +496,7 @@ export const AddApplicabilityStatementDialog = forwardRef<
|
||||
ref,
|
||||
() => ({
|
||||
open: (soaId: string, orgId: string, connId: string) => {
|
||||
setStateOfApplicabilityId(soaId);
|
||||
setStatementOfApplicabilityId(soaId);
|
||||
setOrganizationId(orgId);
|
||||
setConnectionId(connId);
|
||||
dialogRef.current?.open();
|
||||
@@ -506,7 +506,7 @@ export const AddApplicabilityStatementDialog = forwardRef<
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
setStateOfApplicabilityId(null);
|
||||
setStatementOfApplicabilityId(null);
|
||||
setOrganizationId(null);
|
||||
setConnectionId(null);
|
||||
onClose?.();
|
||||
@@ -518,12 +518,12 @@ export const AddApplicabilityStatementDialog = forwardRef<
|
||||
className="max-w-3xl"
|
||||
title={(
|
||||
<Breadcrumb
|
||||
items={[__("States of Applicability"), __("Add Statement")]}
|
||||
items={[__("Statements of Applicability"), __("Add Statement")]}
|
||||
/>
|
||||
)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{stateOfApplicabilityId && organizationId && connectionId
|
||||
{statementOfApplicabilityId && organizationId && connectionId
|
||||
? (
|
||||
<Suspense
|
||||
fallback={(
|
||||
@@ -536,7 +536,7 @@ export const AddApplicabilityStatementDialog = forwardRef<
|
||||
)}
|
||||
>
|
||||
<AddApplicabilityStatementDialogContent
|
||||
stateOfApplicabilityId={stateOfApplicabilityId}
|
||||
statementOfApplicabilityId={statementOfApplicabilityId}
|
||||
organizationId={organizationId}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
@@ -28,19 +28,19 @@ import { graphql } from "react-relay";
|
||||
import { useNavigate } from "react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { CreateStateOfApplicabilityDialogMutation } from "#/__generated__/core/CreateStateOfApplicabilityDialogMutation.graphql";
|
||||
import type { CreateStatementOfApplicabilityDialogMutation } from "#/__generated__/core/CreateStatementOfApplicabilityDialogMutation.graphql";
|
||||
import { PeopleSelectField } from "#/components/form/PeopleSelectField";
|
||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
const createMutation = graphql`
|
||||
mutation CreateStateOfApplicabilityDialogMutation(
|
||||
$input: CreateStateOfApplicabilityInput!
|
||||
mutation CreateStatementOfApplicabilityDialogMutation(
|
||||
$input: CreateStatementOfApplicabilityInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createStateOfApplicability(input: $input) {
|
||||
stateOfApplicabilityEdge @prependEdge(connections: $connections) {
|
||||
createStatementOfApplicability(input: $input) {
|
||||
statementOfApplicabilityEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
name
|
||||
@@ -48,8 +48,8 @@ const createMutation = graphql`
|
||||
snapshotId
|
||||
createdAt
|
||||
updatedAt
|
||||
canDelete: permission(action: "core:state-of-applicability:delete")
|
||||
...StateOfApplicabilityRowFragment
|
||||
canDelete: permission(action: "core:statement-of-applicability:delete")
|
||||
...StatementOfApplicabilityRowFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ const schema = z.object({
|
||||
ownerId: z.string().min(1),
|
||||
});
|
||||
|
||||
export function CreateStateOfApplicabilityDialog({
|
||||
export function CreateStatementOfApplicabilityDialog({
|
||||
children,
|
||||
connectionId,
|
||||
}: Props) {
|
||||
@@ -84,19 +84,19 @@ export function CreateStateOfApplicabilityDialog({
|
||||
);
|
||||
const ref = useDialogRef();
|
||||
|
||||
const [createStateOfApplicability, isCreating]
|
||||
= useMutationWithToasts<CreateStateOfApplicabilityDialogMutation>(
|
||||
const [createStatementOfApplicability, isCreating]
|
||||
= useMutationWithToasts<CreateStatementOfApplicabilityDialogMutation>(
|
||||
createMutation,
|
||||
{
|
||||
successMessage: __(
|
||||
"State of applicability created successfully.",
|
||||
"Statement of applicability created successfully.",
|
||||
),
|
||||
errorMessage: __("Failed to create state of applicability"),
|
||||
errorMessage: __("Failed to create statement of applicability"),
|
||||
},
|
||||
);
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof schema>) => {
|
||||
await createStateOfApplicability({
|
||||
await createStatementOfApplicability({
|
||||
variables: {
|
||||
input: {
|
||||
name: data.name,
|
||||
@@ -108,11 +108,11 @@ export function CreateStateOfApplicabilityDialog({
|
||||
onCompleted: (response) => {
|
||||
reset();
|
||||
ref.current?.close();
|
||||
const stateOfApplicabilityId
|
||||
= response.createStateOfApplicability.stateOfApplicabilityEdge
|
||||
const statementOfApplicabilityId
|
||||
= response.createStatementOfApplicability.statementOfApplicabilityEdge
|
||||
.node.id;
|
||||
void navigate(
|
||||
`/organizations/${organizationId}/states-of-applicability/${stateOfApplicabilityId}`,
|
||||
`/organizations/${organizationId}/statements-of-applicability/${statementOfApplicabilityId}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -125,8 +125,8 @@ export function CreateStateOfApplicabilityDialog({
|
||||
title={(
|
||||
<Breadcrumb
|
||||
items={[
|
||||
__("States of Applicability"),
|
||||
__("New State of Applicability"),
|
||||
__("Statements of Applicability"),
|
||||
__("New Statement of Applicability"),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
@@ -121,7 +121,7 @@ export const EditControlDialog = forwardRef<EditControlDialogRef>((_props, ref)
|
||||
ref={dialogRef}
|
||||
className="max-w-lg"
|
||||
title={
|
||||
<Breadcrumb items={[__("States of Applicability"), __("Edit Statement")]} />
|
||||
<Breadcrumb items={[__("Statements of Applicability"), __("Edit Statement")]} />
|
||||
}
|
||||
>
|
||||
{control
|
||||
@@ -38,9 +38,9 @@ import type { LinkControlDialogQuery } from "#/__generated__/core/LinkControlDia
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
|
||||
const linkControlQuery = graphql`
|
||||
query LinkControlDialogQuery($stateOfApplicabilityId: ID!, $organizationId: ID!) {
|
||||
stateOfApplicability: node(id: $stateOfApplicabilityId) {
|
||||
... on StateOfApplicability {
|
||||
query LinkControlDialogQuery($statementOfApplicabilityId: ID!, $organizationId: ID!) {
|
||||
statementOfApplicability: node(id: $statementOfApplicabilityId) {
|
||||
... on StatementOfApplicability {
|
||||
id
|
||||
applicabilityStatements(first: 10000) {
|
||||
edges {
|
||||
@@ -103,7 +103,7 @@ const unlinkControlMutation = graphql`
|
||||
`;
|
||||
|
||||
export type LinkControlDialogRef = {
|
||||
open: (stateOfApplicabilityId: string, organizationId: string, onUpdate?: () => void) => void;
|
||||
open: (statementOfApplicabilityId: string, organizationId: string, onUpdate?: () => void) => void;
|
||||
};
|
||||
|
||||
type Control = {
|
||||
@@ -119,12 +119,12 @@ type Control = {
|
||||
|
||||
function ControlRow({
|
||||
control,
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
isLinked,
|
||||
onUpdate,
|
||||
}: {
|
||||
control: Control;
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
isLinked: boolean;
|
||||
onUpdate?: () => void;
|
||||
}) {
|
||||
@@ -169,7 +169,7 @@ function ControlRow({
|
||||
await linkMutate({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
controlId: control.controlId,
|
||||
applicability: true,
|
||||
justification: null,
|
||||
@@ -189,7 +189,7 @@ function ControlRow({
|
||||
await linkMutate({
|
||||
variables: {
|
||||
input: {
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
controlId: control.controlId,
|
||||
applicability: false,
|
||||
justification: justification || null,
|
||||
@@ -259,11 +259,11 @@ function ControlRow({
|
||||
}
|
||||
|
||||
function LinkControlDialogContent({
|
||||
stateOfApplicabilityId,
|
||||
statementOfApplicabilityId,
|
||||
organizationId,
|
||||
onUpdate,
|
||||
}: {
|
||||
stateOfApplicabilityId: string;
|
||||
statementOfApplicabilityId: string;
|
||||
organizationId: string;
|
||||
onUpdate?: () => void;
|
||||
}) {
|
||||
@@ -272,10 +272,10 @@ function LinkControlDialogContent({
|
||||
const [collapsedFrameworks, setCollapsedFrameworks] = useState<Set<string>>(new Set());
|
||||
const data = useLazyLoadQuery<LinkControlDialogQuery>(
|
||||
linkControlQuery,
|
||||
{ stateOfApplicabilityId, organizationId },
|
||||
{ statementOfApplicabilityId, organizationId },
|
||||
{ fetchPolicy: "store-or-network" },
|
||||
) as {
|
||||
stateOfApplicability: {
|
||||
statementOfApplicability: {
|
||||
id: string;
|
||||
applicabilityStatements?: {
|
||||
edges: Array<{
|
||||
@@ -309,7 +309,7 @@ function LinkControlDialogContent({
|
||||
// Build a map of control ID -> applicability statement
|
||||
const applicabilityMap = useMemo(() => {
|
||||
const map = new Map<string, { id: string; applicability: boolean; justification: string | null }>();
|
||||
data.stateOfApplicability?.applicabilityStatements?.edges.forEach((edge) => {
|
||||
data.statementOfApplicability?.applicabilityStatements?.edges.forEach((edge) => {
|
||||
map.set(edge.node.control.id, {
|
||||
id: edge.node.id,
|
||||
applicability: edge.node.applicability,
|
||||
@@ -317,7 +317,7 @@ function LinkControlDialogContent({
|
||||
});
|
||||
});
|
||||
return map;
|
||||
}, [data.stateOfApplicability?.applicabilityStatements]);
|
||||
}, [data.statementOfApplicability?.applicabilityStatements]);
|
||||
|
||||
// Merge controls with applicability info
|
||||
const allControls = useMemo(() => {
|
||||
@@ -410,7 +410,7 @@ function LinkControlDialogContent({
|
||||
<ControlRow
|
||||
key={control.controlId}
|
||||
control={control}
|
||||
stateOfApplicabilityId={stateOfApplicabilityId}
|
||||
statementOfApplicabilityId={statementOfApplicabilityId}
|
||||
isLinked={control.applicabilityStatementId !== null}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
@@ -431,13 +431,13 @@ function LinkControlDialogContent({
|
||||
export const LinkControlDialog = forwardRef<LinkControlDialogRef>((_props, ref) => {
|
||||
const { __ } = useTranslate();
|
||||
const dialogRef = useDialogRef();
|
||||
const [stateOfApplicabilityId, setStateOfApplicabilityId] = useState<string | null>(null);
|
||||
const [statementOfApplicabilityId, setStatementOfApplicabilityId] = useState<string | null>(null);
|
||||
const [organizationId, setOrganizationId] = useState<string | null>(null);
|
||||
const [onUpdateCallback, setOnUpdateCallback] = useState<(() => void) | undefined>(undefined);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: (soaId: string, orgId: string, callback?: () => void) => {
|
||||
setStateOfApplicabilityId(soaId);
|
||||
setStatementOfApplicabilityId(soaId);
|
||||
setOrganizationId(orgId);
|
||||
setOnUpdateCallback(() => callback);
|
||||
dialogRef.current?.open();
|
||||
@@ -445,7 +445,7 @@ export const LinkControlDialog = forwardRef<LinkControlDialogRef>((_props, ref)
|
||||
}), [dialogRef]);
|
||||
|
||||
const handleClose = () => {
|
||||
setStateOfApplicabilityId(null);
|
||||
setStatementOfApplicabilityId(null);
|
||||
setOrganizationId(null);
|
||||
setOnUpdateCallback(undefined);
|
||||
};
|
||||
@@ -456,12 +456,12 @@ export const LinkControlDialog = forwardRef<LinkControlDialogRef>((_props, ref)
|
||||
className="max-w-3xl"
|
||||
title={(
|
||||
<Breadcrumb
|
||||
items={[__("States of Applicability"), __("Add Controls")]}
|
||||
items={[__("Statements of Applicability"), __("Add Controls")]}
|
||||
/>
|
||||
)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{stateOfApplicabilityId && organizationId
|
||||
{statementOfApplicabilityId && organizationId
|
||||
? (
|
||||
<Suspense
|
||||
fallback={(
|
||||
@@ -471,7 +471,7 @@ export const LinkControlDialog = forwardRef<LinkControlDialogRef>((_props, ref)
|
||||
)}
|
||||
>
|
||||
<LinkControlDialogContent
|
||||
stateOfApplicabilityId={stateOfApplicabilityId}
|
||||
statementOfApplicabilityId={statementOfApplicabilityId}
|
||||
organizationId={organizationId}
|
||||
onUpdate={onUpdateCallback}
|
||||
/>
|
||||
@@ -32,7 +32,7 @@ import { Suspense, useCallback, useRef } from "react";
|
||||
import { graphql, useFragment, useRelayEnvironment } from "react-relay";
|
||||
import { fetchQuery } from "relay-runtime";
|
||||
|
||||
import type { StateOfApplicabilityControlsTabFragment$key } from "#/__generated__/core/StateOfApplicabilityControlsTabFragment.graphql";
|
||||
import type { StatementOfApplicabilityControlsTabFragment$key } from "#/__generated__/core/StatementOfApplicabilityControlsTabFragment.graphql";
|
||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
@@ -46,17 +46,17 @@ import {
|
||||
} from "../dialogs/EditControlDialog";
|
||||
|
||||
const refetchStatementsQuery = graphql`
|
||||
query StateOfApplicabilityControlsTabRefetchQuery($stateOfApplicabilityId: ID!) {
|
||||
node(id: $stateOfApplicabilityId) {
|
||||
... on StateOfApplicability {
|
||||
...StateOfApplicabilityControlsTabFragment
|
||||
query StatementOfApplicabilityControlsTabRefetchQuery($statementOfApplicabilityId: ID!) {
|
||||
node(id: $statementOfApplicabilityId) {
|
||||
... on StatementOfApplicability {
|
||||
...StatementOfApplicabilityControlsTabFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const controlsFragment = graphql`
|
||||
fragment StateOfApplicabilityControlsTabFragment on StateOfApplicability {
|
||||
fragment StatementOfApplicabilityControlsTabFragment on StatementOfApplicability {
|
||||
id
|
||||
organization {
|
||||
id
|
||||
@@ -73,7 +73,7 @@ export const controlsFragment = graphql`
|
||||
)
|
||||
|
||||
applicabilityStatements(first: 1000, orderBy: { direction: ASC, field: CONTROL_SECTION_TITLE })
|
||||
@connection(key: "StateOfApplicabilityControlsTab_applicabilityStatements") {
|
||||
@connection(key: "StatementOfApplicabilityControlsTab_applicabilityStatements") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
@@ -105,7 +105,7 @@ export const controlsFragment = graphql`
|
||||
`;
|
||||
|
||||
const deleteApplicabilityStatementMutation = graphql`
|
||||
mutation StateOfApplicabilityControlsTabDeleteMutation(
|
||||
mutation StatementOfApplicabilityControlsTabDeleteMutation(
|
||||
$input: DeleteApplicabilityStatementInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
@@ -115,17 +115,17 @@ const deleteApplicabilityStatementMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function StateOfApplicabilityControlsTab({
|
||||
stateOfApplicability,
|
||||
export default function StatementOfApplicabilityControlsTab({
|
||||
statementOfApplicability,
|
||||
isSnapshotMode = false,
|
||||
}: {
|
||||
stateOfApplicability: StateOfApplicabilityControlsTabFragment$key & {
|
||||
statementOfApplicability: StatementOfApplicabilityControlsTabFragment$key & {
|
||||
id: string;
|
||||
};
|
||||
isSnapshotMode?: boolean;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const data = useFragment(controlsFragment, stateOfApplicability);
|
||||
const data = useFragment(controlsFragment, statementOfApplicability);
|
||||
const environment = useRelayEnvironment();
|
||||
const organizationId = useOrganizationId();
|
||||
const addStatementDialogRef = useRef<AddApplicabilityStatementDialogRef>(null);
|
||||
@@ -134,7 +134,7 @@ export default function StateOfApplicabilityControlsTab({
|
||||
const handleDialogClose = useCallback(() => {
|
||||
if (!data.id) return;
|
||||
fetchQuery(environment, refetchStatementsQuery, {
|
||||
stateOfApplicabilityId: data.id,
|
||||
statementOfApplicabilityId: data.id,
|
||||
}).subscribe({});
|
||||
}, [environment, data.id]);
|
||||
|
||||
@@ -45,7 +45,7 @@ import { processingActivityRoutes } from "./routes/processingActivityRoutes";
|
||||
import { rightsRequestRoutes } from "./routes/rightsRequestRoutes";
|
||||
import { riskRoutes } from "./routes/riskRoutes";
|
||||
import { snapshotsRoutes } from "./routes/snapshotsRoutes";
|
||||
import { statesOfApplicabilityRoutes } from "./routes/statesOfApplicabilityRoutes";
|
||||
import { statementsOfApplicabilityRoutes } from "./routes/statementsOfApplicabilityRoutes";
|
||||
import { taskRoutes } from "./routes/taskRoutes";
|
||||
import { vendorRoutes } from "./routes/vendorRoutes";
|
||||
|
||||
@@ -288,7 +288,7 @@ const routes = [
|
||||
...obligationRoutes,
|
||||
...rightsRequestRoutes,
|
||||
...processingActivityRoutes,
|
||||
...statesOfApplicabilityRoutes,
|
||||
...statementsOfApplicabilityRoutes,
|
||||
...accessReviewRoutes,
|
||||
...compliancePageRoutes,
|
||||
...snapshotsRoutes,
|
||||
|
||||
@@ -17,33 +17,33 @@ import type { AppRoute } from "@probo/routes";
|
||||
|
||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||
|
||||
export const statesOfApplicabilityRoutes = [
|
||||
export const statementsOfApplicabilityRoutes = [
|
||||
{
|
||||
path: "states-of-applicability",
|
||||
path: "statements-of-applicability",
|
||||
Fallback: PageSkeleton,
|
||||
Component: lazy(
|
||||
() => import("#/pages/organizations/states-of-applicability/StatesOfApplicabilityPageLoader"),
|
||||
() => import("#/pages/organizations/statements-of-applicability/StatementsOfApplicabilityPageLoader"),
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "snapshots/:snapshotId/states-of-applicability",
|
||||
path: "snapshots/:snapshotId/statements-of-applicability",
|
||||
Fallback: PageSkeleton,
|
||||
Component: lazy(
|
||||
() => import("#/pages/organizations/states-of-applicability/StatesOfApplicabilityPageLoader"),
|
||||
() => import("#/pages/organizations/statements-of-applicability/StatementsOfApplicabilityPageLoader"),
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "states-of-applicability/:stateOfApplicabilityId",
|
||||
path: "statements-of-applicability/:statementOfApplicabilityId",
|
||||
Fallback: PageSkeleton,
|
||||
Component: lazy(
|
||||
() => import("#/pages/organizations/states-of-applicability/StateOfApplicabilityDetailPageLoader"),
|
||||
() => import("#/pages/organizations/statements-of-applicability/StatementOfApplicabilityDetailPageLoader"),
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "snapshots/:snapshotId/states-of-applicability/:stateOfApplicabilityId",
|
||||
path: "snapshots/:snapshotId/statements-of-applicability/:statementOfApplicabilityId",
|
||||
Fallback: PageSkeleton,
|
||||
Component: lazy(
|
||||
() => import("#/pages/organizations/states-of-applicability/StateOfApplicabilityDetailPageLoader"),
|
||||
() => import("#/pages/organizations/statements-of-applicability/StatementOfApplicabilityDetailPageLoader"),
|
||||
),
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
Reference in New Issue
Block a user