From 1196b791dd4e920fd6200f3c0b7edc73d0d2bdc0 Mon Sep 17 00:00:00 2001 From: gearnode Date: Thu, 10 Apr 2025 10:01:26 -0700 Subject: [PATCH] Add ability to link policies to controls Signed-off-by: gearnode --- CHANGELOG.md | 8 +- .../frameworks/controls/Control.tsx | 575 ++++++++++++++++++ ...trolCreatePolicyMappingMutation.graphql.ts | 93 +++ ...trolDeletePolicyMappingMutation.graphql.ts | 93 +++ .../ControlLinkedPoliciesQuery.graphql.ts | 271 +++++++++ ...ontrolOrganizationPoliciesQuery.graphql.ts | 271 +++++++++ pkg/coredata/control_policy.go | 4 +- pkg/coredata/policy.go | 1 - 8 files changed, 1310 insertions(+), 6 deletions(-) create mode 100644 apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlCreatePolicyMappingMutation.graphql.ts create mode 100644 apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlDeletePolicyMappingMutation.graphql.ts create mode 100644 apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlLinkedPoliciesQuery.graphql.ts create mode 100644 apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlOrganizationPoliciesQuery.graphql.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7c93127..7cee3db48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -## [0.4.2] - 2025-04-09 - ### Added - Add vendor compliance reports UI +- Controls can now be linked to policies, enabling better organization of compliance documentation and clearer traceability between policies and security controls +- New UI for viewing and managing policies related to a specific control + +## [0.4.2] - 2025-04-09 ### Changed -- Simplified policy data model by removing version field and optimistic concurrency +- Simplified policy data model by removing version field and optimistic concurrency - Refactored policy update flow to load-modify-save pattern ### Fixed diff --git a/apps/console/src/pages/organizations/frameworks/controls/Control.tsx b/apps/console/src/pages/organizations/frameworks/controls/Control.tsx index 463b65e61..1a1a8a956 100644 --- a/apps/console/src/pages/organizations/frameworks/controls/Control.tsx +++ b/apps/console/src/pages/organizations/frameworks/controls/Control.tsx @@ -34,6 +34,14 @@ import { ControlOrganizationMitigationsQuery, } from "./__generated__/ControlOrganizationMitigationsQuery.graphql"; import { ControlFragment_Control$key } from "./__generated__/ControlFragment_Control.graphql"; +import { + ControlLinkedPoliciesQuery$data, + ControlLinkedPoliciesQuery, +} from "./__generated__/ControlLinkedPoliciesQuery.graphql"; +import { + ControlOrganizationPoliciesQuery$data, + ControlOrganizationPoliciesQuery, +} from "./__generated__/ControlOrganizationPoliciesQuery.graphql"; const controlFragment = graphql` fragment ControlFragment_Control on Control { @@ -90,6 +98,50 @@ const organizationMitigationsQuery = graphql` } `; +// Query to fetch linked policies +const linkedPoliciesQuery = graphql` + query ControlLinkedPoliciesQuery($controlId: ID!) { + control: node(id: $controlId) { + id + ... on Control { + policies(first: 100) @connection(key: "Control__policies") { + edges { + node { + id + name + content + status + reviewDate + } + } + } + } + } + } +`; + +// Query to fetch all policies for the organization +const organizationPoliciesQuery = graphql` + query ControlOrganizationPoliciesQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + id + ... on Organization { + policies(first: 100) @connection(key: "Organization__policies") { + edges { + node { + id + name + content + status + reviewDate + } + } + } + } + } + } +`; + // Mutation to create a mapping between a control and a mitigation const createMitigationMappingMutation = graphql` mutation ControlCreateMitigationMappingMutation( @@ -112,6 +164,28 @@ const deleteMitigationMappingMutation = graphql` } `; +// Mutation to create a mapping between a control and a policy +const createPolicyMappingMutation = graphql` + mutation ControlCreatePolicyMappingMutation( + $input: CreateControlPolicyMappingInput! + ) { + createControlPolicyMapping(input: $input) { + success + } + } +`; + +// Mutation to delete a mapping between a control and a policy +const deletePolicyMappingMutation = graphql` + mutation ControlDeletePolicyMappingMutation( + $input: DeleteControlPolicyMappingInput! + ) { + deleteControlPolicyMapping(input: $input) { + success + } + } +`; + export function Control({ controlKey, }: { @@ -138,6 +212,18 @@ export function Control({ const [isUnlinkingMitigation, setIsUnlinkingMitigation] = useState(false); const [categoryFilter, setCategoryFilter] = useState(null); + // Policy state + const [isPolicyMappingDialogOpen, setIsPolicyMappingDialogOpen] = + useState(false); + const [linkedPoliciesData, setLinkedPoliciesData] = + useState(null); + const [organizationPoliciesData, setOrganizationPoliciesData] = + useState(null); + const [policySearchQuery, setPolicySearchQuery] = useState(""); + const [isLoadingPolicies, setIsLoadingPolicies] = useState(false); + const [isLinkingPolicy, setIsLinkingPolicy] = useState(false); + const [isUnlinkingPolicy, setIsUnlinkingPolicy] = useState(false); + // Create mutation hooks const [commitCreateMitigationMapping] = useMutation( createMitigationMappingMutation @@ -145,6 +231,8 @@ export function Control({ const [commitDeleteMitigationMapping] = useMutation( deleteMitigationMappingMutation ); + const [commitCreatePolicyMapping] = useMutation(createPolicyMappingMutation); + const [commitDeletePolicyMapping] = useMutation(deletePolicyMappingMutation); // Load initial linked mitigations data useEffect(() => { @@ -415,6 +503,243 @@ export function Control({ setIsMitigationMappingDialogOpen(true); }, [loadMitigationsData]); + // Load initial linked policies data + useEffect(() => { + if (control.id) { + setIsLoadingPolicies(true); + fetchQuery(environment, linkedPoliciesQuery, { + controlId: control.id, + }).subscribe({ + next: (data) => { + setLinkedPoliciesData(data); + setIsLoadingPolicies(false); + }, + error: (error: Error) => { + console.error("Error loading initial policies:", error); + setIsLoadingPolicies(false); + }, + }); + } + }, [control.id, environment]); + + // Load policies data + const loadPoliciesData = useCallback(() => { + if (!organizationId || !control.id) return; + + setIsLoadingPolicies(true); + + // Fetch all policies for the organization + fetchQuery( + environment, + organizationPoliciesQuery, + { + organizationId, + } + ).subscribe({ + next: (data) => { + setOrganizationPoliciesData(data); + }, + complete: () => { + // Fetch linked policies for this control + fetchQuery( + environment, + linkedPoliciesQuery, + { + controlId: control.id, + } + ).subscribe({ + next: (data) => { + setLinkedPoliciesData(data); + setIsLoadingPolicies(false); + }, + error: (error: Error) => { + console.error("Error fetching linked policies:", error); + setIsLoadingPolicies(false); + toast({ + title: "Error", + description: "Failed to load linked policies.", + variant: "destructive", + }); + }, + }); + }, + error: (error: Error) => { + console.error("Error fetching organization policies:", error); + setIsLoadingPolicies(false); + toast({ + title: "Error", + description: "Failed to load policies.", + variant: "destructive", + }); + }, + }); + }, [control.id, environment, organizationId, toast]); + + // Policy helper functions + const getPolicies = useCallback(() => { + if (!organizationPoliciesData?.organization?.policies?.edges) return []; + return organizationPoliciesData.organization.policies.edges.map( + (edge) => edge.node + ); + }, [organizationPoliciesData]); + + const getLinkedPolicies = useCallback(() => { + if (!linkedPoliciesData?.control?.policies?.edges) return []; + return linkedPoliciesData.control.policies.edges.map((edge) => edge.node); + }, [linkedPoliciesData]); + + const isPolicyLinked = useCallback( + (policyId: string) => { + const linkedPolicies = getLinkedPolicies(); + return linkedPolicies.some((policy) => policy.id === policyId); + }, + [getLinkedPolicies] + ); + + const filteredPolicies = useCallback(() => { + const policies = getPolicies(); + if (!policySearchQuery) return policies; + + return policies.filter((policy) => { + return ( + !policySearchQuery || + policy.name.toLowerCase().includes(policySearchQuery.toLowerCase()) || + (policy.content && + policy.content + .toLowerCase() + .includes(policySearchQuery.toLowerCase())) + ); + }); + }, [getPolicies, policySearchQuery]); + + // Policy link/unlink handlers + const handleLinkPolicy = useCallback( + (policyId: string) => { + if (!control.id) return; + + setIsLinkingPolicy(true); + + commitCreatePolicyMapping({ + variables: { + input: { + controlId: control.id, + policyId: policyId, + }, + }, + onCompleted: (_, errors) => { + setIsLinkingPolicy(false); + + if (errors) { + console.error("Error linking policy:", errors); + toast({ + title: "Error", + description: "Failed to link policy. Please try again.", + variant: "destructive", + }); + return; + } + + // Refresh linked policies data + fetchQuery( + environment, + linkedPoliciesQuery, + { + controlId: control.id, + } + ).subscribe({ + next: (data) => { + setLinkedPoliciesData(data); + }, + error: (error: Error) => { + console.error("Error refreshing linked policies:", error); + }, + }); + + toast({ + title: "Success", + description: "Policy successfully linked to control.", + }); + }, + onError: (error) => { + setIsLinkingPolicy(false); + console.error("Error linking policy:", error); + toast({ + title: "Error", + description: "Failed to link policy. Please try again.", + variant: "destructive", + }); + }, + }); + }, + [commitCreatePolicyMapping, control.id, environment, toast] + ); + + const handleUnlinkPolicy = useCallback( + (policyId: string) => { + if (!control.id) return; + + setIsUnlinkingPolicy(true); + + commitDeletePolicyMapping({ + variables: { + input: { + controlId: control.id, + policyId: policyId, + }, + }, + onCompleted: (_, errors) => { + setIsUnlinkingPolicy(false); + + if (errors) { + console.error("Error unlinking policy:", errors); + toast({ + title: "Error", + description: "Failed to unlink policy. Please try again.", + variant: "destructive", + }); + return; + } + + // Refresh linked policies data + fetchQuery( + environment, + linkedPoliciesQuery, + { + controlId: control.id, + } + ).subscribe({ + next: (data) => { + setLinkedPoliciesData(data); + }, + error: (error: Error) => { + console.error("Error refreshing linked policies:", error); + }, + }); + + toast({ + title: "Success", + description: "Policy successfully unlinked from control.", + }); + }, + onError: (error) => { + setIsUnlinkingPolicy(false); + console.error("Error unlinking policy:", error); + toast({ + title: "Error", + description: "Failed to unlink policy. Please try again.", + variant: "destructive", + }); + }, + }); + }, + [commitDeletePolicyMapping, control.id, environment, toast] + ); + + const handleOpenPolicyMappingDialog = useCallback(() => { + loadPoliciesData(); + setIsPolicyMappingDialogOpen(true); + }, [loadPoliciesData]); + // UI helper functions const formatImportance = (importance: string | undefined): string => { if (!importance) return "Unknown"; @@ -773,6 +1098,256 @@ export function Control({ )} + + {/* Policies Section */} +
+ {/* Policy Mapping Dialog */} + + + + Link Policies to Control + + Search and select policies to link to this control. This helps + track which policies address this control. + + + +
+
+
+ + setPolicySearchQuery(e.target.value)} + className="w-full pl-10" + /> +
+
+
+ +
+ {isLoadingPolicies ? ( +
+ + Loading policies... +
+ ) : ( +
+ {filteredPolicies().length === 0 ? ( +
+ No policies found. Try adjusting your search. +
+ ) : ( + + + + + + + + + + + {filteredPolicies().map((policy) => { + const isLinked = isPolicyLinked(policy.id); + return ( + + + + + + + ); + })} + +
NameStatus + Review Date + + Actions +
+
+ {policy.name} +
+ {policy.content && ( +
+ {policy.content} +
+ )} +
+
+ {policy.status} +
+
+ {policy.reviewDate + ? new Date( + policy.reviewDate + ).toLocaleDateString() + : "Not set"} + + {isLinked ? ( + + ) : ( + + )} +
+ )} +
+ )} +
+ + + + +
+
+ + {/* Linked Policies List */} +
+
+

Policies

+ +
+ + {isLoadingPolicies ? ( +
+ + Loading policies... +
+ ) : linkedPoliciesData?.control?.policies?.edges && + linkedPoliciesData.control.policies.edges.length > 0 ? ( +
+ + + + + + + + + + + {getLinkedPolicies().map((policy) => ( + + + + + + + ))} + +
NameStatusReview Date + Actions +
+
{policy.name}
+ {policy.content && ( +
+ {policy.content} +
+ )} +
+
+ {policy.status} +
+
+ {policy.reviewDate + ? new Date(policy.reviewDate).toLocaleDateString() + : "Not set"} + +
+ + +
+
+
+ ) : ( +
+ No policies linked to this control yet. Click "Link + Policies" to connect some. +
+ )} +
+
); diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlCreatePolicyMappingMutation.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlCreatePolicyMappingMutation.graphql.ts new file mode 100644 index 000000000..337208e01 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlCreatePolicyMappingMutation.graphql.ts @@ -0,0 +1,93 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type CreateControlPolicyMappingInput = { + controlId: string; + policyId: string; +}; +export type ControlCreatePolicyMappingMutation$variables = { + input: CreateControlPolicyMappingInput; +}; +export type ControlCreatePolicyMappingMutation$data = { + readonly createControlPolicyMapping: { + readonly success: boolean; + }; +}; +export type ControlCreatePolicyMappingMutation = { + response: ControlCreatePolicyMappingMutation$data; + variables: ControlCreatePolicyMappingMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "CreateControlPolicyMappingPayload", + "kind": "LinkedField", + "name": "createControlPolicyMapping", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "success", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ControlCreatePolicyMappingMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ControlCreatePolicyMappingMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "bde220ef49867cec6b77bffc06878258", + "id": null, + "metadata": {}, + "name": "ControlCreatePolicyMappingMutation", + "operationKind": "mutation", + "text": "mutation ControlCreatePolicyMappingMutation(\n $input: CreateControlPolicyMappingInput!\n) {\n createControlPolicyMapping(input: $input) {\n success\n }\n}\n" + } +}; +})(); + +(node as any).hash = "8e6ef41146efb59340331e16ce0493d6"; + +export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlDeletePolicyMappingMutation.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlDeletePolicyMappingMutation.graphql.ts new file mode 100644 index 000000000..f2675a061 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlDeletePolicyMappingMutation.graphql.ts @@ -0,0 +1,93 @@ +/** + * @generated SignedSource<<5988b98a6e2ae42a565e51fc13fa5c91>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteControlPolicyMappingInput = { + controlId: string; + policyId: string; +}; +export type ControlDeletePolicyMappingMutation$variables = { + input: DeleteControlPolicyMappingInput; +}; +export type ControlDeletePolicyMappingMutation$data = { + readonly deleteControlPolicyMapping: { + readonly success: boolean; + }; +}; +export type ControlDeletePolicyMappingMutation = { + response: ControlDeletePolicyMappingMutation$data; + variables: ControlDeletePolicyMappingMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "DeleteControlPolicyMappingPayload", + "kind": "LinkedField", + "name": "deleteControlPolicyMapping", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "success", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ControlDeletePolicyMappingMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ControlDeletePolicyMappingMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "3b84cd899eee67303695eae81d1ba453", + "id": null, + "metadata": {}, + "name": "ControlDeletePolicyMappingMutation", + "operationKind": "mutation", + "text": "mutation ControlDeletePolicyMappingMutation(\n $input: DeleteControlPolicyMappingInput!\n) {\n deleteControlPolicyMapping(input: $input) {\n success\n }\n}\n" + } +}; +})(); + +(node as any).hash = "e706f52dac09ef3fcadeb588ccd0274d"; + +export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlLinkedPoliciesQuery.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlLinkedPoliciesQuery.graphql.ts new file mode 100644 index 000000000..c31c8654e --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlLinkedPoliciesQuery.graphql.ts @@ -0,0 +1,271 @@ +/** + * @generated SignedSource<<9bddb18bf52f14881a85995793bdc1ca>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type PolicyStatus = "ACTIVE" | "DRAFT"; +export type ControlLinkedPoliciesQuery$variables = { + controlId: string; +}; +export type ControlLinkedPoliciesQuery$data = { + readonly control: { + readonly id: string; + readonly policies?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly content: string; + readonly id: string; + readonly name: string; + readonly reviewDate: string | null | undefined; + readonly status: PolicyStatus; + }; + }>; + }; + }; +}; +export type ControlLinkedPoliciesQuery = { + response: ControlLinkedPoliciesQuery$data; + variables: ControlLinkedPoliciesQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "controlId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "controlId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v4 = [ + { + "alias": null, + "args": null, + "concreteType": "PolicyEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Policy", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "content", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "reviewDate", + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } +], +v5 = [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ControlLinkedPoliciesQuery", + "selections": [ + { + "alias": "control", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "policies", + "args": null, + "concreteType": "PolicyConnection", + "kind": "LinkedField", + "name": "__Control__policies_connection", + "plural": false, + "selections": (v4/*: any*/), + "storageKey": null + } + ], + "type": "Control", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ControlLinkedPoliciesQuery", + "selections": [ + { + "alias": "control", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v5/*: any*/), + "concreteType": "PolicyConnection", + "kind": "LinkedField", + "name": "policies", + "plural": false, + "selections": (v4/*: any*/), + "storageKey": "policies(first:100)" + }, + { + "alias": null, + "args": (v5/*: any*/), + "filters": null, + "handle": "connection", + "key": "Control__policies", + "kind": "LinkedHandle", + "name": "policies" + } + ], + "type": "Control", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "67f9bf1d5f255f5406e932abde540e08", + "id": null, + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "control", + "policies" + ] + } + ] + }, + "name": "ControlLinkedPoliciesQuery", + "operationKind": "query", + "text": "query ControlLinkedPoliciesQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n status\n reviewDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "3904c2344754177177fa7c16129dc894"; + +export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlOrganizationPoliciesQuery.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlOrganizationPoliciesQuery.graphql.ts new file mode 100644 index 000000000..ac483608f --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlOrganizationPoliciesQuery.graphql.ts @@ -0,0 +1,271 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type PolicyStatus = "ACTIVE" | "DRAFT"; +export type ControlOrganizationPoliciesQuery$variables = { + organizationId: string; +}; +export type ControlOrganizationPoliciesQuery$data = { + readonly organization: { + readonly id: string; + readonly policies?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly content: string; + readonly id: string; + readonly name: string; + readonly reviewDate: string | null | undefined; + readonly status: PolicyStatus; + }; + }>; + }; + }; +}; +export type ControlOrganizationPoliciesQuery = { + response: ControlOrganizationPoliciesQuery$data; + variables: ControlOrganizationPoliciesQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v4 = [ + { + "alias": null, + "args": null, + "concreteType": "PolicyEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Policy", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "content", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "reviewDate", + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } +], +v5 = [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ControlOrganizationPoliciesQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "policies", + "args": null, + "concreteType": "PolicyConnection", + "kind": "LinkedField", + "name": "__Organization__policies_connection", + "plural": false, + "selections": (v4/*: any*/), + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ControlOrganizationPoliciesQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v5/*: any*/), + "concreteType": "PolicyConnection", + "kind": "LinkedField", + "name": "policies", + "plural": false, + "selections": (v4/*: any*/), + "storageKey": "policies(first:100)" + }, + { + "alias": null, + "args": (v5/*: any*/), + "filters": null, + "handle": "connection", + "key": "Organization__policies", + "kind": "LinkedHandle", + "name": "policies" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "3a3c91f3e18e8a4b66011aa583e262e4", + "id": null, + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "organization", + "policies" + ] + } + ] + }, + "name": "ControlOrganizationPoliciesQuery", + "operationKind": "query", + "text": "query ControlOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n status\n reviewDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "f4699d729e6df6192efd201047689204"; + +export default node; diff --git a/pkg/coredata/control_policy.go b/pkg/coredata/control_policy.go index 4eaec6715..7626d44da 100644 --- a/pkg/coredata/control_policy.go +++ b/pkg/coredata/control_policy.go @@ -43,7 +43,7 @@ func (cp ControlPolicy) Insert( ) error { q := ` INSERT INTO - controls_mitigations ( + controls_policies ( control_id, policy_id, tenant_id, @@ -75,7 +75,7 @@ func (cp ControlPolicy) Delete( q := ` DELETE FROM - controls_mitigations + controls_policies WHERE %s AND control_id = @control_id diff --git a/pkg/coredata/policy.go b/pkg/coredata/policy.go index badc2f9b6..082e81d4c 100644 --- a/pkg/coredata/policy.go +++ b/pkg/coredata/policy.go @@ -266,7 +266,6 @@ WITH plcs AS ( ) SELECT id, - tenant_id, organization_id, owner_id, name,