@@ -1,9 +1,10 @@
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { Suspense, useEffect, useState, useRef } from "react";
|
||||
import {
|
||||
graphql,
|
||||
PreloadedQuery,
|
||||
usePreloadedQuery,
|
||||
useQueryLoader,
|
||||
useMutation,
|
||||
} from "react-relay";
|
||||
import { useParams, useNavigate, Link } from "react-router";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -20,16 +21,17 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { MitigationListViewQuery as MitigationListViewQueryType } from "./__generated__/MitigationListViewQuery.graphql";
|
||||
import { MitigationListViewSkeleton } from "./MitigationListPage";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { MitigationListViewImportMitigationMutation as MitigationListViewImportMitigationMutationType } from "./__generated__/MitigationListViewImportMitigationMutation.graphql";
|
||||
|
||||
const mitigationListViewQuery = graphql`
|
||||
query MitigationListViewQuery($organizationId: ID!, $first: Int) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
mitigations(
|
||||
first: $first
|
||||
orderBy: { direction: ASC, field: CREATED_AT }
|
||||
) @connection(key: "MitigationListView_mitigations") {
|
||||
mitigations(first: $first)
|
||||
@connection(key: "MitigationListView_mitigations") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
@@ -48,6 +50,28 @@ const mitigationListViewQuery = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const importMitigationMutation = graphql`
|
||||
mutation MitigationListViewImportMitigationMutation(
|
||||
$input: ImportMitigationInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
importMitigation(input: $input) {
|
||||
mitigationEdges @appendEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
category
|
||||
state
|
||||
importance
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Mitigation {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@@ -93,6 +117,14 @@ function MitigationListContent({
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { organizationId } = useParams();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const [importMitigation] =
|
||||
useMutation<MitigationListViewImportMitigationMutationType>(
|
||||
importMitigationMutation
|
||||
);
|
||||
|
||||
// Monitor URL hash for changes and update state accordingly
|
||||
const [hashValue, setHashValue] = useState(window.location.hash);
|
||||
@@ -249,18 +281,75 @@ function MitigationListContent({
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
setIsImporting(true);
|
||||
|
||||
importMitigation({
|
||||
variables: {
|
||||
connections: [data.organization.mitigations.__id],
|
||||
input: {
|
||||
organizationId: organizationId!,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
uploadables: {
|
||||
"input.file": file,
|
||||
},
|
||||
onCompleted: () => {
|
||||
setIsImporting(false);
|
||||
toast({
|
||||
title: "Mitigations imported",
|
||||
description: "Mitigations have been imported successfully.",
|
||||
variant: "default",
|
||||
});
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
setIsImporting(false);
|
||||
toast({
|
||||
title: "Error importing mitigations",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title="Mitigations"
|
||||
description="Mitigations are actions taken to reduce the risk. Add them to track their implementation status."
|
||||
actions={
|
||||
<Button asChild>
|
||||
<Link to={`/organizations/${organizationId}/mitigations/new`}>
|
||||
New Mitigation
|
||||
</Link>
|
||||
</Button>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={isImporting}
|
||||
>
|
||||
{isImporting ? "Importing..." : "Import Mitigations"}
|
||||
</Button>
|
||||
<Button asChild>
|
||||
<Link to={`/organizations/${organizationId}/mitigations/new`}>
|
||||
New Mitigation
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{/* Hidden file input for mitigation import */}
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileChange}
|
||||
style={{ display: "none" }}
|
||||
accept=".json"
|
||||
/>
|
||||
|
||||
{/* Global Progress Summary */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* @generated SignedSource<<d4590a13a15cf248104340854bc46aa7>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type MitigationImportance = "ADVANCED" | "MANDATORY" | "PREFERRED";
|
||||
export type MitigationState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
export type ImportMitigationInput = {
|
||||
file: any;
|
||||
organizationId: string;
|
||||
};
|
||||
export type MitigationListViewImportMitigationMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: ImportMitigationInput;
|
||||
};
|
||||
export type MitigationListViewImportMitigationMutation$data = {
|
||||
readonly importMitigation: {
|
||||
readonly mitigationEdges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly category: string;
|
||||
readonly createdAt: string;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly importance: MitigationImportance;
|
||||
readonly name: string;
|
||||
readonly state: MitigationState;
|
||||
readonly updatedAt: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
export type MitigationListViewImportMitigationMutation = {
|
||||
response: MitigationListViewImportMitigationMutation$data;
|
||||
variables: MitigationListViewImportMitigationMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MitigationEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "mitigationEdges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Mitigation",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "importance",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "MitigationListViewImportMitigationMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "ImportMitigationPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "importMitigation",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "MitigationListViewImportMitigationMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "ImportMitigationPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "importMitigation",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "appendEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "mitigationEdges",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "84c038c572378999bfaacb87e8172b1e",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MitigationListViewImportMitigationMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation MitigationListViewImportMitigationMutation(\n $input: ImportMitigationInput!\n) {\n importMitigation(input: $input) {\n mitigationEdges {\n node {\n id\n name\n description\n category\n state\n importance\n createdAt\n updatedAt\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "59e3d4a8e71cee0dcee606b93d706dad";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<c919ae742bdab52e5e68fb5c7fe92812>>
|
||||
* @generated SignedSource<<a98cd84786da5d6229cb66bc24ad92d0>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -19,6 +19,7 @@ export type MitigationListViewQuery$data = {
|
||||
readonly organization: {
|
||||
readonly id: string;
|
||||
readonly mitigations?: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly category: string;
|
||||
@@ -65,21 +66,13 @@ v3 = {
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"kind": "Literal",
|
||||
"name": "orderBy",
|
||||
"value": {
|
||||
"direction": "ASC",
|
||||
"field": "CREATED_AT"
|
||||
}
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = [
|
||||
v5 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -146,7 +139,7 @@ v6 = [
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/)
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -184,15 +177,26 @@ v6 = [
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
v7 = [
|
||||
v6 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "first",
|
||||
"variableName": "first"
|
||||
},
|
||||
(v4/*: any*/)
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
@@ -218,15 +222,13 @@ return {
|
||||
"selections": [
|
||||
{
|
||||
"alias": "mitigations",
|
||||
"args": [
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"args": null,
|
||||
"concreteType": "MitigationConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__MitigationListView_mitigations_connection",
|
||||
"plural": false,
|
||||
"selections": (v6/*: any*/),
|
||||
"storageKey": "__MitigationListView_mitigations_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})"
|
||||
"selections": (v5/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
@@ -256,27 +258,25 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v5/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "MitigationConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "mitigations",
|
||||
"plural": false,
|
||||
"selections": (v6/*: any*/),
|
||||
"selections": (v5/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"filters": [
|
||||
"orderBy"
|
||||
],
|
||||
"args": (v6/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "MitigationListView_mitigations",
|
||||
"kind": "LinkedHandle",
|
||||
@@ -292,7 +292,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "cb49e5e7740ec71ce0b8e1abbdbcf24d",
|
||||
"cacheID": "9951f5e4be50df7a883af63426fe5fde",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -309,11 +309,11 @@ return {
|
||||
},
|
||||
"name": "MitigationListViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query MitigationListViewQuery(\n $organizationId: ID!\n $first: Int\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n mitigations(first: $first, orderBy: {direction: ASC, field: CREATED_AT}) {\n edges {\n node {\n id\n name\n description\n category\n state\n importance\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
"text": "query MitigationListViewQuery(\n $organizationId: ID!\n $first: Int\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n mitigations(first: $first) {\n edges {\n node {\n id\n name\n description\n category\n state\n importance\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "ee7fdf984d5cb376068ba53d0a56c59b";
|
||||
(node as any).hash = "c2da6b42a986dd18cd18603c5f5809ad";
|
||||
|
||||
export default node;
|
||||
|
||||
Reference in New Issue
Block a user