= ({
items={
linkedThreats.map(x => ({
label: x.statement,
- dismissLabel: `Unlink threat ${x.numericId}`,
+ dismissLabel: `${t('Unlink threat')} ${x.numericId}`,
}))
}
onDismiss={({ detail: { itemIndex } }) => {
diff --git a/packages/threat-composer/src/components/threats/ThreatStatementCard/index.tsx b/packages/threat-composer/src/components/threats/ThreatStatementCard/index.tsx
index 4ad9b0ae..f50e79ad 100644
--- a/packages/threat-composer/src/components/threats/ThreatStatementCard/index.tsx
+++ b/packages/threat-composer/src/components/threats/ThreatStatementCard/index.tsx
@@ -15,15 +15,20 @@
******************************************************************************************************************** */
/** @jsxImportSource @emotion/react */
import { SpaceBetween } from '@cloudscape-design/components';
-import ButtonDropdown, { ButtonDropdownProps } from '@cloudscape-design/components/button-dropdown';
+import ButtonDropdown, {
+ ButtonDropdownProps,
+} from '@cloudscape-design/components/button-dropdown';
import ColumnLayout from '@cloudscape-design/components/column-layout';
import { OptionDefinition } from '@cloudscape-design/components/internal/components/option/interfaces';
import { CancelableEventHandler } from '@cloudscape-design/components/internal/events';
import TextContent from '@cloudscape-design/components/text-content';
import { FC, useCallback, useMemo } from 'react';
+import AutoDirectionContainer from '../../../components/generic/AutoDirectionContainer';
+import LocalizationContainer from '../../../components/generic/LocalizationContainer';
import { THREAT_STATUS_COLOR_MAPPING } from '../../../configs/status';
import { TemplateThreatStatement } from '../../../customTypes';
import threatStatus from '../../../data/status/threatStatus.json';
+import { useReloadedTranslation } from '../../../i18next';
import AssumptionLink from '../../assumptions/AssumptionLink';
import CopyToClipbord from '../../generic/CopyToClipboard';
import GenericCard from '../../generic/GenericCard';
@@ -38,10 +43,23 @@ export interface ThreatStatementCardProps {
onCopy?: (id: string) => void;
onRemove?: (id: string) => void;
onEditInWizard?: (id: string) => void;
- onEditStatementStatus: (statement: TemplateThreatStatement, status: string) => void;
- onEditMetadata: (statement: TemplateThreatStatement, key: string, value: string | string[] | undefined) => void;
- onAddTagToStatement?: (statement: TemplateThreatStatement, tag: string) => void;
- onRemoveTagFromStatement?: (statement: TemplateThreatStatement, tag: string) => void;
+ onEditStatementStatus: (
+ statement: TemplateThreatStatement,
+ status: string
+ ) => void;
+ onEditMetadata: (
+ statement: TemplateThreatStatement,
+ key: string,
+ value: string | string[] | undefined
+ ) => void;
+ onAddTagToStatement?: (
+ statement: TemplateThreatStatement,
+ tag: string
+ ) => void;
+ onRemoveTagFromStatement?: (
+ statement: TemplateThreatStatement,
+ tag: string
+ ) => void;
}
const ThreatStatementCard: FC
= ({
@@ -55,83 +73,127 @@ const ThreatStatementCard: FC = ({
onEditStatementStatus,
onEditMetadata,
}) => {
- const handleMoreActions: CancelableEventHandler = useCallback(({ detail }) => {
- switch (detail.id) {
- case 'copyToCurrentWorkspace':
- onCopy?.(statement.id);
- break;
- default:
- console.log('Unknown action', detail.id);
- }
- }, [onCopy, statement.id]);
+ const { t, i18n } = useReloadedTranslation();
+
+ const handleMoreActions: CancelableEventHandler =
+ useCallback(
+ ({ detail }) => {
+ switch (detail.id) {
+ case 'copyToCurrentWorkspace':
+ onCopy?.(statement.id);
+ break;
+ default:
+ console.log('Unknown action', detail.id);
+ }
+ },
+ [onCopy, statement.id],
+ );
const moreActions = useMemo(() => {
return (
);
+ />
+ );
}, []);
const displayStatement = useMemo(() => {
if (statement.displayedStatement) {
- return statement.displayedStatement.map((s, index) => typeof s === 'string' ?
- s : s.type === 'b' ?
- {s.content} :
- s.content);
+ return statement.displayedStatement.map((s, index) =>
+ typeof s === 'string' ? (
+ s
+ ) : s.type === 'b' ? (
+ {s.content}
+ ) : (
+ s.content
+ ),
+ );
}
return statement.statement || '';
}, [statement]);
- return (
- onEditStatementStatus(statement, option)}
- selectedOption={statement.status}
- statusColorMapping={THREAT_STATUS_COLOR_MAPPING}
- />
-
- }
- tags={statement.tags}
- moreActions={moreActions}
- onRemove={onRemove}
- onEdit={onEditInWizard}
- onAddTagToEntity={(_entityId, tag) => onAddTagToStatement?.(statement, tag)}
- onRemoveTagFromEntity={(_entityId, tag) => onRemoveTagFromStatement?.(statement, tag)}
- >
-
-
-
-
- {displayStatement}
-
-
- {showLinkedEntities &&
-
- {
+ if (statement.displayedStatement) {
+ return statement.displayedStatement
+ .map((s, _index) =>
+ typeof s === 'string' ? s : s.type === 'b' ? s.content : s.content,
+ )
+ .join(' ');
+ }
+
+ return statement.statement || '';
+ }, [statement]);
+
+ const threatStatusTranslated = useMemo(
+ () =>
+ threatStatus.map((item) => {
+ return { ...item, description: t(item.description) };
+ }),
+ [threatStatus, t],
+ ) as OptionDefinition[];
+
+ return (
+
+
+
+ onEditStatementStatus(statement, option)
+ }
+ selectedOption={statement.status}
+ statusColorMapping={THREAT_STATUS_COLOR_MAPPING}
+ />
+
+
+ }
+ tags={statement.tags}
+ moreActions={moreActions}
+ onRemove={onRemove}
+ onEdit={onEditInWizard}
+ onAddTagToEntity={(_entityId, tag) =>
+ onAddTagToStatement?.(statement, tag)
+ }
+ onRemoveTagFromEntity={(_entityId, tag) =>
+ onRemoveTagFromStatement?.(statement, tag)
+ }
+ >
+
+
+
+
+
+ {displayStatement}
+
+
+
+ {showLinkedEntities && (
+
+
+
+
+ )}
+
+
- }
-
-
-
- );
+
+
+
+ );
};
-export default ThreatStatementCard;
\ No newline at end of file
+export default ThreatStatementCard;
diff --git a/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx b/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx
index 9883d3cd..903885e0 100644
--- a/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx
+++ b/packages/threat-composer/src/components/threats/ThreatStatementEditor/index.tsx
@@ -22,6 +22,8 @@ import * as awsui from '@cloudscape-design/design-tokens';
import { css } from '@emotion/react';
import React, { FC, useCallback, useMemo, useState, useRef, useEffect, ReactNode, PropsWithChildren } from 'react';
import { EditorProps } from './types';
+import AutoDirectionContainer from '../../../components/generic/AutoDirectionContainer';
+import LocalizationContainer from '../../../components/generic/LocalizationContainer';
import { METADATA_KEY_SOURCE, METADATA_KEY_SOURCE_THREAT_PACK, METADATA_KEY_SOURCE_THREAT_PACK_MITIGATION_CANDIDATE, METADATA_KEY_SOURCE_THREAT_PACK_THREAT } from '../../../configs';
import { DEFAULT_NEW_ENTITY_ID, DEFAULT_WORKSPACE_LABEL } from '../../../configs/constants';
import { useAssumptionLinksContext } from '../../../contexts/AssumptionLinksContext/context';
@@ -31,12 +33,11 @@ import { useMitigationLinksContext } from '../../../contexts/MitigationLinksCont
import { useMitigationsContext } from '../../../contexts/MitigationsContext/context';
import { useThreatsContext } from '../../../contexts/ThreatsContext/context';
import { useWorkspacesContext } from '../../../contexts/WorkspacesContext/context';
-import { Mitigation, TemplateThreatStatement, ViewNavigationEvent } from '../../../customTypes';
+import { Mitigation, TemplateThreatStatement, ThreatStatementFormat, ViewNavigationEvent } from '../../../customTypes';
import { ThreatFieldTypes } from '../../../customTypes/threatFieldTypes';
import threatFieldData from '../../../data/threatFieldData';
-import threatStatementExamples from '../../../data/threatStatementExamples.json';
-import threatStatementFormat from '../../../data/threatStatementFormat';
import useEditMetadata from '../../../hooks/useEditMetadata';
+import { useReloadedTranslation } from '../../../i18next';
import getMetadata from '../../../utils/getMetadata';
import getNewMitigation from '../../../utils/getNewMitigation';
import getNewThreatStatement from '../../../utils/getNewThreatStatement';
@@ -75,7 +76,6 @@ const styles = {
}),
};
-const defaultThreatStatementFormat = threatStatementFormat[63];
export interface ThreatStatementEditorProps {
onThreatListView?: ViewNavigationEvent['onThreatListView'];
@@ -143,6 +143,10 @@ export const ThreatStatementEditorInner: FC {
return getLinkedAssumptionLinks(editingStatement.id).map(la => la.assumptionId);
}, [getLinkedAssumptionLinks, editingStatement]);
@@ -161,6 +165,17 @@ export const ThreatStatementEditorInner: FC {
+ return (t('THREAT_STATEMENT_EXAMPLE_DATA', { returnObjects: true }) as TemplateThreatStatement[]).map(e => {
+ const { statement, displayedStatement } = renderThreatStatement(e, t);
+ return {
+ ...e,
+ statement,
+ displayedStatement,
+ };
+ });
+ }, [t, i18n, i18n.language]);
+
const Component = useMemo(() => {
return editor && editorMapping[editor];
}, [editor]);
@@ -176,7 +191,7 @@ export const ThreatStatementEditorInner: FC {
if (editingStatement) {
- const { statement, suggestions: currentSuggestions, displayedStatement } = renderThreatStatement(editingStatement);
+ const { statement, suggestions: currentSuggestions, displayedStatement } = renderThreatStatement(editingStatement, t);
if (statement !== editingStatement.statement) {
setEditingStatement(prev => prev && ({
...prev,
@@ -197,7 +212,7 @@ export const ThreatStatementEditorInner: FC {
setEditingStatement(getNewThreatStatement());
@@ -291,11 +306,11 @@ export const ThreatStatementEditorInner: FC {
if (!currentWorkspace && workspaceList.length === 0) {
// For most of use cases, if there is only the default workspace, use list to reduce cognitive load.
- return editingStatement?.numericId === -1 ? 'Add to list' : 'Save';
+ return editingStatement?.numericId === -1 ? t('Add to list') : t('Save');
}
const workspace = currentWorkspace?.name || DEFAULT_WORKSPACE_LABEL;
- return editingStatement?.numericId === -1 ? `Add to workspace ${workspace}` : `Save to workspace ${workspace}`;
+ return editingStatement?.numericId === -1 ? `${t('Add to workspace')} ${workspace}` : `${t('Save to workspace')} ${workspace}`;
}, [currentWorkspace, workspaceList, editingStatement]);
@@ -383,35 +398,58 @@ export const ThreatStatementEditorInner: FCNot threat statement editing in place;
}
+ const displayStatementAsString: string = useMemo(() => {
+ if (editingStatement.displayedStatement) {
+ return editingStatement.displayedStatement.map((s, _index) =>
+ typeof s === 'string' ? (
+ s
+ ) : s.type === 'b' ? (
+ s.content
+ ) : (
+ s.content
+ ),
+ ).join(' ');
+ }
+
+ return editingStatement.statement || '';
+ }, [editingStatement]);
+
return (
-
-
- {composerMode === 'ThreatsOnly' && }
-
-
- {Component && editor &&
+
+
+
+ {composerMode === 'ThreatsOnly' && }
+
+
+
+
+
+
+
+
+ {Component && editor &&
@@ -425,53 +463,54 @@ export const ThreatStatementEditorInner: FC
setEditor(token as ThreatFieldTypes)} />
}
- {composerMode === 'Full' &&
- setLinkedMitigationIds(prev => prev.filter(p => p !== id))}
- >
-
+ setLinkedMitigationIds(prev => prev.filter(p => p !== id))}
+ >
+
+
+
}
+ {composerMode === 'Full' &&
+
setLinkedAssumptionIds(prev => prev.filter(p => p !== id))}
/>
-
- }
- {composerMode === 'Full' &&
-
setLinkedAssumptionIds(prev => prev.filter(p => p !== id))}
- />
- }
- {composerMode === 'Full' &&
- setEditingStatement((prev => ({
- ...prev,
- status,
- } as TemplateThreatStatement)))}
- onEditMetadata={handleEditMetadata}
- />
-
}
- {isExampleVisible && }
-
- {customTemplateEditorVisible && }
- );
+ }
+ {composerMode === 'Full' &&