Skip to content

Commit

Permalink
feat(ThreatModelView): Allow users to download the markdown file (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
jessieweiyi authored Aug 29, 2023
1 parent 1887092 commit 1b14a6c
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { css } from '@emotion/react';
import { FC, useEffect, useCallback, useState, ReactNode } from 'react';
import { DataExchangeFormat, HasContentDetails, ViewNavigationEvent } from '../../../../../customTypes';
import printStyles from '../../../../../styles/print';
import downloadContentAsMarkdown from '../../../../../utils/downloadObjectAsMarkdown';
import sanitizeHtml from '../../../../../utils/sanitizeHtml';
import MarkdownViewer from '../../../../generic/MarkdownViewer';
import { getApplicationInfoContent } from '../../utils/getApplicationInfo';
Expand Down Expand Up @@ -56,13 +57,15 @@ const styles = {
export interface ThreatModelViewProps extends ViewNavigationEvent {
composerMode: string;
data: DataExchangeFormat;
downloadFileName?: string;
onPrintButtonClick?: () => void;
hasContentDetails?: HasContentDetails;
}

const ThreatModelView: FC<ThreatModelViewProps> = ({
data,
composerMode,
downloadFileName,
onPrintButtonClick,
hasContentDetails,
...props
Expand All @@ -75,14 +78,14 @@ const ThreatModelView: FC<ThreatModelViewProps> = ({
setLoading(true);
const sanitizedData = sanitizeHtml(data);
const processedContent = (composerMode === 'Full' ? [
hasContentDetails?.applicationName && await getApplicationName(sanitizedData),
hasContentDetails?.applicationInfo && await getApplicationInfoContent(sanitizedData),
hasContentDetails?.architecture && await getArchitectureContent(sanitizedData),
hasContentDetails?.dataflow && await getDataflowContent(sanitizedData),
hasContentDetails?.assumptions && await getAssumptionsContent(sanitizedData),
hasContentDetails?.threats && await getThreatsContent(sanitizedData),
hasContentDetails?.mitigations && await getMitigationsContent(sanitizedData),
hasContentDetails?.threats && await getAssetsContent(sanitizedData),
(!hasContentDetails || hasContentDetails.applicationName) && await getApplicationName(sanitizedData),
(!hasContentDetails || hasContentDetails.applicationInfo) && await getApplicationInfoContent(sanitizedData),
(!hasContentDetails || hasContentDetails.architecture) && await getArchitectureContent(sanitizedData),
(!hasContentDetails || hasContentDetails.dataflow) && await getDataflowContent(sanitizedData),
(!hasContentDetails || hasContentDetails.assumptions) && await getAssumptionsContent(sanitizedData),
(!hasContentDetails || hasContentDetails.threats) && await getThreatsContent(sanitizedData),
(!hasContentDetails || hasContentDetails.mitigations) && await getMitigationsContent(sanitizedData),
(!hasContentDetails || hasContentDetails.threats) && await getAssetsContent(sanitizedData),
] : [await getThreatsContent(sanitizedData, true)]).filter(x => !!x).join('\n');

setContent(processedContent);
Expand All @@ -96,6 +99,10 @@ const ThreatModelView: FC<ThreatModelViewProps> = ({
await navigator.clipboard.writeText(content);
}, [content]);

const handleDownloadMarkdown = useCallback(() => {
downloadFileName && downloadContentAsMarkdown(content, downloadFileName);
}, [content, downloadFileName]);

const getNextStepButtons = useCallback(() => {
const buttons: ReactNode[] = [];
if (!hasContentDetails?.applicationInfo) {
Expand Down Expand Up @@ -141,6 +148,10 @@ const ThreatModelView: FC<ThreatModelViewProps> = ({
Copy as Markdown
</Button>
</Popover>
{downloadFileName && <Button
onClick={handleDownloadMarkdown}>
Download as Markdown File
</Button>}
<Button variant="primary" onClick={onPrintButtonClick || (() => window.print())}>Print</Button>
</SpaceBetween>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
******************************************************************************************************************** */
import { FC } from 'react';
import { FC, useMemo } from 'react';
import ThreatModelView from './components/ThreatModelView';
import { useGlobalSetupContext, useWorkspacesContext } from '../../../contexts';
import useImportExport from '../../../hooks/useExportImport';
import useHasContent from '../../../hooks/useHasContent';
import getExportFileName from '../../../utils/getExportFileName';

export interface ThreatModelProps {
onPrintButtonClick?: () => void;
Expand All @@ -29,6 +30,12 @@ const ThreatModel: FC<ThreatModelProps> = ({
const { getWorkspaceData } = useImportExport();
const { composerMode } = useGlobalSetupContext();
const [_, hasContentDetails] = useHasContent();
const { currentWorkspace } = useWorkspacesContext();

const downloadFileName = useMemo(() => {
return getExportFileName(composerMode, false, currentWorkspace);
}, [composerMode, currentWorkspace]);

const {
onApplicationInfoView,
onArchitectureView,
Expand All @@ -41,6 +48,7 @@ const ThreatModel: FC<ThreatModelProps> = ({
onPrintButtonClick={onPrintButtonClick}
composerMode={composerMode}
data={getWorkspaceData()}
downloadFileName={downloadFileName}
hasContentDetails={hasContentDetails}
onApplicationInfoView={onApplicationInfoView}
onArchitectureView={onArchitectureView}
Expand Down
9 changes: 2 additions & 7 deletions packages/threat-composer/src/hooks/useExportImport/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
limitations under the License.
******************************************************************************************************************** */
import { useCallback } from 'react';
import { EXPORT_FILE_NAME } from '../../configs/export';
import { useWorkspacesContext } from '../../contexts';
import { useApplicationInfoContext } from '../../contexts/ApplicationContext/context';
import { useArchitectureInfoContext } from '../../contexts/ArchitectureContext/context';
Expand All @@ -25,18 +24,14 @@ import { useGlobalSetupContext } from '../../contexts/GlobalSetupContext/context
import { useMitigationLinksContext } from '../../contexts/MitigationLinksContext/context';
import { useMitigationsContext } from '../../contexts/MitigationsContext/context';
import { useThreatsContext } from '../../contexts/ThreatsContext/context';
import { ComposerMode, DataExchangeFormat, TemplateThreatStatement, Workspace } from '../../customTypes';
import { DataExchangeFormat, TemplateThreatStatement } from '../../customTypes';
import downloadObjectAsJson from '../../utils/downloadObjectAsJson';
import getExportFileName from '../../utils/getExportFileName';
import sanitizeHtml from '../../utils/sanitizeHtml';
import validateData from '../../utils/validateData';

const SCHEMA_VERSION = 1.0;

const getExportFileName = (composerMode: ComposerMode, filtered: boolean, currentWorkspace: Workspace | null) => {
const exportFileName = `${EXPORT_FILE_NAME}_Workspace_${currentWorkspace ? currentWorkspace.name.replace(' ', '-') : 'Default'}${composerMode !== 'Full' ? '_ThreatsOnly' : ''}${filtered ? '_Filtered' : ''}`;
return exportFileName;
};

const useImportExport = () => {
const { composerMode } = useGlobalSetupContext();
const { currentWorkspace } = useWorkspacesContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ const downloadObjectAsJson = (exportObj: any, exportName: string) => {
downloadAnchorNode.remove();
};


export default downloadObjectAsJson;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** *******************************************************************************************************************
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.
******************************************************************************************************************** */
const downloadContentAsMarkdown = (content: any, exportName: string) => {
var dataStr = 'data:text/markdown;charset=utf-8,' + encodeURIComponent(content);
var downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute('href', dataStr);
downloadAnchorNode.setAttribute('download', exportName + '.md');
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
};

export default downloadContentAsMarkdown;
24 changes: 24 additions & 0 deletions packages/threat-composer/src/utils/getExportFileName/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** *******************************************************************************************************************
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 { EXPORT_FILE_NAME } from '../../configs/export';
import { ComposerMode, Workspace } from '../../customTypes';

const getExportFileName = (composerMode: ComposerMode, filtered: boolean, currentWorkspace: Workspace | null) => {
const exportFileName = `${EXPORT_FILE_NAME}_Workspace_${currentWorkspace ? currentWorkspace.name.replace(' ', '-') : 'Default'}${composerMode !== 'Full' ? '_ThreatsOnly' : ''}${filtered ? '_Filtered' : ''}`;
return exportFileName;
};

export default getExportFileName;

0 comments on commit 1b14a6c

Please sign in to comment.