diff --git a/packages/threat-composer-app/src/containers/MitigationList/index.tsx b/packages/threat-composer-app/src/containers/MitigationList/index.tsx index 28d3f97b..a3c4c90b 100644 --- a/packages/threat-composer-app/src/containers/MitigationList/index.tsx +++ b/packages/threat-composer-app/src/containers/MitigationList/index.tsx @@ -14,9 +14,13 @@ limitations under the License. ******************************************************************************************************************** */ import { MitigationList as MitigationListComponent } from '@aws/threat-composer'; +import { useLocation } from 'react-router-dom'; const MitigationList = () => { - return ; + const { state } = useLocation(); + return ; }; export default MitigationList; \ No newline at end of file diff --git a/packages/threat-composer-app/src/containers/WorkspaceHome/index.tsx b/packages/threat-composer-app/src/containers/WorkspaceHome/index.tsx index d014f52d..e587d852 100644 --- a/packages/threat-composer-app/src/containers/WorkspaceHome/index.tsx +++ b/packages/threat-composer-app/src/containers/WorkspaceHome/index.tsx @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ******************************************************************************************************************** */ -import { WorkspaceHome as WorkspaceHomeComponent, ThreatStatementListFilter } from '@aws/threat-composer'; -import { ROUTE_APPLICATION_INFO, ROUTE_THREAT_EDITOR, ROUTE_THREAT_LIST } from '../../config/routes'; +import { WorkspaceHome as WorkspaceHomeComponent, ThreatStatementListFilter, MitigationListFilter } from '@aws/threat-composer'; +import { ROUTE_APPLICATION_INFO, ROUTE_MITIGATION_LIST, ROUTE_THREAT_EDITOR, ROUTE_THREAT_LIST } from '../../config/routes'; import useNavigateView from '../../hooks/useNavigationView'; const WorkspaceHome = () => { @@ -33,6 +33,11 @@ const WorkspaceHome = () => { filter, } : undefined, })} + onMitigationListView={(filter?: MitigationListFilter) => handleNavigateView(ROUTE_MITIGATION_LIST, undefined, undefined, undefined, { + state: filter ? { + filter, + } : undefined, + })} />; }; diff --git a/packages/threat-composer-app/src/utils/convertToDocx/getMitigations.ts b/packages/threat-composer-app/src/utils/convertToDocx/getMitigations.ts index 2b7fa535..2521e5f8 100644 --- a/packages/threat-composer-app/src/utils/convertToDocx/getMitigations.ts +++ b/packages/threat-composer-app/src/utils/convertToDocx/getMitigations.ts @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ******************************************************************************************************************** */ -import { Mitigation, MitigationLink, AssumptionLink, DataExchangeFormat, standardizeNumericId } from '@aws/threat-composer'; +import { Mitigation, MitigationLink, AssumptionLink, DataExchangeFormat, standardizeNumericId, mitigationStatus, STATUS_NOT_SET } from '@aws/threat-composer'; import { Paragraph, HeadingLevel, TextRun, TableCell, TableRow } from 'docx'; import Table from './components/Table'; import getAnchorLink from './getAnchorLink'; @@ -64,7 +64,7 @@ const getDataRow = async (mitigation: Mitigation, data: DataExchangeFormat) => { const mitigationId = `M-${standardizeNumericId(mitigation.numericId)}`; const threatLinks = data.mitigationLinks?.filter(ml => ml.mitigationId === mitigation.id) || []; const assumptionLinks = data.assumptionLinks?.filter(al => al.linkedId === mitigation.id) || []; - + const status = (mitigation.status && mitigationStatus.find(ms => ms.value === mitigation.status)?.label) || STATUS_NOT_SET; const renderedComents = await renderComment(mitigation.metadata); const tableRow = new TableRow({ @@ -81,6 +81,9 @@ const getDataRow = async (mitigation: Mitigation, data: DataExchangeFormat) => { }), getThreatLinksCell(threatLinks, data), getAssumptionLinksCell(assumptionLinks, data), + new TableCell({ + children: [new Paragraph(status)], + }), new TableCell({ children: renderedComents, }), @@ -111,7 +114,7 @@ const getMitigations = async ( const table = new Table({ rows: [ - getHeaderRow(['Mitigation Number', 'Mitigation', 'Threats Mitigating', 'Assumptions', 'Comments']), + getHeaderRow(['Mitigation Number', 'Mitigation', 'Threats Mitigating', 'Assumptions', 'Status', 'Comments']), ...dataRows, ], }); diff --git a/packages/threat-composer-app/src/utils/convertToDocx/getThreats.ts b/packages/threat-composer-app/src/utils/convertToDocx/getThreats.ts index 4c23727c..ba5c819b 100644 --- a/packages/threat-composer-app/src/utils/convertToDocx/getThreats.ts +++ b/packages/threat-composer-app/src/utils/convertToDocx/getThreats.ts @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ******************************************************************************************************************** */ -import { AssumptionLink, DataExchangeFormat, MitigationLink, TemplateThreatStatement, standardizeNumericId } from '@aws/threat-composer'; +import { AssumptionLink, DataExchangeFormat, MitigationLink, TemplateThreatStatement, standardizeNumericId, threatStatus, STATUS_NOT_SET } from '@aws/threat-composer'; import { Paragraph, HeadingLevel, TextRun, TableRow, TableCell } from 'docx'; import Table from './components/Table'; import getAnchorLink from './getAnchorLink'; @@ -64,6 +64,7 @@ const getDataRow = async (threat: TemplateThreatStatement, data: DataExchangeFor const assumptionLinks = data.assumptionLinks?.filter(al => al.linkedId === threat.id) || []; const threatId = `T-${standardizeNumericId(threat.numericId)}`; const comments = await renderComment(threat.metadata); + const status = (threat.status && threatStatus.find(x => x.value === threat.status)?.label) || STATUS_NOT_SET; const priority = threat.metadata?.find(m => m.key === 'Priority')?.value as string || ''; const STRIDE = ((threat.metadata?.find(m => m.key === 'STRIDE')?.value || []) as string[]).join(', '); @@ -81,6 +82,9 @@ const getDataRow = async (threat: TemplateThreatStatement, data: DataExchangeFor }), getMitigationLinksCell(mitigationLinks, data), getAssumptionLinksCell(assumptionLinks, data), + new TableCell({ + children: [new Paragraph(status)], + }), new TableCell({ children: [new Paragraph(priority)], }), @@ -119,8 +123,8 @@ const getThreats = async ( const table = new Table({ rows: [ getHeaderRow( - threatsOnly ? ['Threat Number', 'Threat', 'Comments', 'Priority', 'STRIDE', 'Comments'] - : ['Threat Number', 'Threat', 'Mitigations', 'Assumptions', 'Priority', 'STRIDE', 'Comments']), + threatsOnly ? ['Threat Number', 'Threat', 'Status', 'Priority', 'STRIDE', 'Comments'] + : ['Threat Number', 'Threat', 'Mitigations', 'Assumptions', 'Status', 'Priority', 'STRIDE', 'Comments']), ...dataRows, ], }); diff --git a/packages/threat-composer/src/components/assumptions/AssumptionCreationCard/index.tsx b/packages/threat-composer/src/components/assumptions/AssumptionCreationCard/index.tsx index ac78d75a..30265d1f 100644 --- a/packages/threat-composer/src/components/assumptions/AssumptionCreationCard/index.tsx +++ b/packages/threat-composer/src/components/assumptions/AssumptionCreationCard/index.tsx @@ -15,11 +15,12 @@ ******************************************************************************************************************** */ import SpaceBetween from '@cloudscape-design/components/space-between'; import { FC, useState, useCallback } from 'react'; -import { DEFAULT_NEW_ENTITY_ID } from '../../../configs'; import { useMitigationsContext } from '../../../contexts/MitigationsContext/context'; import { useThreatsContext } from '../../../contexts/ThreatsContext/context'; import { Assumption, AssumptionSchema } from '../../../customTypes'; -import GenericEntityCreationCard, { DEFAULT_ENTITY } from '../../generic/GenericEntityCreationCard'; +import getNewAssumption from '../../../utils/getNewAssumption'; +import getNewMitigation from '../../../utils/getNewMitigation'; +import GenericEntityCreationCard from '../../generic/GenericEntityCreationCard'; import MitigationLinkView from '../../mitigations/MitigationLinkView'; import ThreatLinkView from '../../threats/ThreatLinkView'; @@ -28,7 +29,7 @@ export interface AssumptionCreationCardProps { } const AssumptionCreationCard: FC = ({ onSave }) => { - const [editingEntity, setEditingEntity] = useState(DEFAULT_ENTITY); + const [editingEntity, setEditingEntity] = useState(getNewAssumption()); const [linkedMitigationIds, setLinkedMitigationIds] = useState([]); const [linkedThreatIds, setLinkedThreatIds] = useState([]); @@ -37,13 +38,13 @@ const AssumptionCreationCard: FC = ({ onSave }) => const handleSave = useCallback(() => { onSave?.(editingEntity, linkedMitigationIds, linkedThreatIds); - setEditingEntity(DEFAULT_ENTITY); + setEditingEntity(getNewAssumption()); setLinkedMitigationIds([]); setLinkedThreatIds([]); }, [editingEntity, linkedMitigationIds, linkedThreatIds]); const handleReset = useCallback(() => { - setEditingEntity(DEFAULT_ENTITY); + setEditingEntity(getNewAssumption()); setLinkedMitigationIds([]); setLinkedThreatIds([]); }, []); @@ -52,11 +53,7 @@ const AssumptionCreationCard: FC = ({ onSave }) => if (mitigationList.find(m => m.id === mitigationIdOrNewMitigation)) { setLinkedMitigationIds(prev => [...prev, mitigationIdOrNewMitigation]); } else { - const newMitigation = saveMitigation({ - numericId: -1, - content: mitigationIdOrNewMitigation, - id: DEFAULT_NEW_ENTITY_ID, - }); + const newMitigation = saveMitigation(getNewMitigation(mitigationIdOrNewMitigation)); setLinkedMitigationIds(prev => [...prev, newMitigation.id]); } }, [mitigationList, saveMitigation]); diff --git a/packages/threat-composer/src/components/assumptions/AssumptionMitigationLink/index.tsx b/packages/threat-composer/src/components/assumptions/AssumptionMitigationLink/index.tsx index 5bd99d18..310f5b32 100644 --- a/packages/threat-composer/src/components/assumptions/AssumptionMitigationLink/index.tsx +++ b/packages/threat-composer/src/components/assumptions/AssumptionMitigationLink/index.tsx @@ -14,10 +14,10 @@ limitations under the License. ******************************************************************************************************************** */ import { FC, useCallback, useEffect, useState } from 'react'; -import { DEFAULT_NEW_ENTITY_ID } from '../../../configs'; import { useAssumptionLinksContext } from '../../../contexts/AssumptionLinksContext/context'; import { useMitigationsContext } from '../../../contexts/MitigationsContext/context'; import { AssumptionLink } from '../../../customTypes'; +import getNewMitigation from '../../../utils/getNewMitigation'; import MitigationLinkView from '../../mitigations/MitigationLinkView'; export interface AssumptionThreatLinkProps { @@ -50,11 +50,7 @@ const AssumptionThreatLinkComponent: FC = ({ type: 'Mitigation', }); } else { - const newMitigation = saveMitigation({ - numericId: -1, - content: mitigationIdOrNewMitigation, - id: DEFAULT_NEW_ENTITY_ID, - }); + const newMitigation = saveMitigation(getNewMitigation(mitigationIdOrNewMitigation)); addAssumptionLink({ type: 'Mitigation', linkedId: newMitigation.id, diff --git a/packages/threat-composer/src/components/generic/DashboardNumber/index.tsx b/packages/threat-composer/src/components/generic/DashboardNumber/index.tsx index ed741761..8da5c489 100644 --- a/packages/threat-composer/src/components/generic/DashboardNumber/index.tsx +++ b/packages/threat-composer/src/components/generic/DashboardNumber/index.tsx @@ -30,22 +30,23 @@ const styles = { }; export interface DashboardNumberProps { - displayedNumber: number; + featuredNumber: number; + totalNumber?: number; showWarning?: boolean; onLinkClicked?: LinkProps['onFollow']; } const DashboardNumber: FC = ({ - showWarning, displayedNumber, onLinkClicked, + showWarning, featuredNumber, totalNumber, onLinkClicked, }) => { return
- {showWarning && displayedNumber > 0 ? ( + {showWarning && (totalNumber ? featuredNumber < totalNumber : featuredNumber > 0) ? ( - {displayedNumber} + {totalNumber ? `${featuredNumber}/${totalNumber}` : featuredNumber} @@ -54,7 +55,7 @@ const DashboardNumber: FC = ({ variant="awsui-value-large" href="#" onFollow={onLinkClicked}> - {displayedNumber} + {totalNumber ? `${featuredNumber}/${totalNumber}` : featuredNumber} )}
; diff --git a/packages/threat-composer/src/components/generic/GenericEntityCreationCard/index.tsx b/packages/threat-composer/src/components/generic/GenericEntityCreationCard/index.tsx index bca9abca..5faded7f 100644 --- a/packages/threat-composer/src/components/generic/GenericEntityCreationCard/index.tsx +++ b/packages/threat-composer/src/components/generic/GenericEntityCreationCard/index.tsx @@ -35,12 +35,6 @@ export interface GenericEntityCreationCardProps { validateData?: TextAreaProps['validateData']; } -export const DEFAULT_ENTITY = { - id: DEFAULT_NEW_ENTITY_ID, - numericId: -1, - content: '', -}; - const GenericEntityCreationCard: FC = ({ editingEntity, setEditingEntity, diff --git a/packages/threat-composer/src/components/generic/StatusBadge/index.tsx b/packages/threat-composer/src/components/generic/StatusBadge/index.tsx new file mode 100644 index 00000000..a8d4d34a --- /dev/null +++ b/packages/threat-composer/src/components/generic/StatusBadge/index.tsx @@ -0,0 +1,72 @@ +/** ******************************************************************************************************************* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + ******************************************************************************************************************** */ +/** @jsxImportSource @emotion/react */ +import Badge, { BadgeProps } from '@cloudscape-design/components/badge'; +import { SelectProps } from '@cloudscape-design/components/select'; +import * as awsui from '@cloudscape-design/design-tokens'; +import { css } from '@emotion/react'; +import { FC, useMemo, useState, useRef } from 'react'; +import StatusSelector, { StatusSelectorProps } from '../StatusSelector'; + +export interface StatusBadgeProps extends Omit { + statusColorMapping: { + [status: string]: BadgeProps['color']; + }; +} + +const StatusBadge: FC = ({ + selectedOption, + setSelectedOption, + options, + statusColorMapping, +}) => { + const ref = useRef(); + + const [editMode, setEditMode] = useState(false); + + const editor = useMemo(() => { + return setEditMode(false)} + />; + }, [options, selectedOption, setSelectedOption]); + + return
{editMode ? (editor) : + ()}
; +}; + +export default StatusBadge; \ No newline at end of file diff --git a/packages/threat-composer/src/components/generic/StatusSelector/index.tsx b/packages/threat-composer/src/components/generic/StatusSelector/index.tsx new file mode 100644 index 00000000..263456bd --- /dev/null +++ b/packages/threat-composer/src/components/generic/StatusSelector/index.tsx @@ -0,0 +1,55 @@ +/** ******************************************************************************************************************* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + ******************************************************************************************************************** */ +import FormField from '@cloudscape-design/components/form-field'; +import Select, { SelectProps } from '@cloudscape-design/components/select'; +import React, { FC } from 'react'; + +export interface StatusSelectorProps { + options: SelectProps.Option[]; + selectedOption?: string; + setSelectedOption?: (option: string) => void; + showLabel?: boolean; + onFocus?: () => void; + onBlur?: () => void; + ref?: React.ForwardedRef; +} + +const StatusSelector: FC = React.forwardRef(({ + options, + selectedOption, + setSelectedOption, + showLabel = true, + onFocus, + onBlur, +}, ref) => { + return ( +