Skip to content

Commit

Permalink
chore: Add window api for returning current workspace data in markdow…
Browse files Browse the repository at this point in the history
…n format
  • Loading branch information
jessieweiyi committed Jul 3, 2024
1 parent 7372d75 commit 2de502e
Show file tree
Hide file tree
Showing 22 changed files with 166 additions and 129 deletions.
14 changes: 12 additions & 2 deletions packages/threat-composer-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,30 @@
See the License for the specific language governing permissions and
limitations under the License.
******************************************************************************************************************** */
import { ThemeProvider, Mode } from '@aws/threat-composer';
import { ThemeProvider, Mode, LOCAL_STORAGE_KEY_THEME } from '@aws/threat-composer';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';
import reportWebVitals from './reportWebVitals';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import isMemoryRouterUsed from './utils/isMemoryRouterUsed';

//For the ide-extension, the theme can be set via meta tag.
const initialThemeString = (document.querySelector('meta[name="dark-mode"]') as HTMLMetaElement)?.content;

const initialTheme = initialThemeString ?
let initialTheme = initialThemeString ?
(initialThemeString === 'true' ? Mode.Dark : Mode.Light) :
undefined;

if (!initialTheme) {
//For other use cases, the value of theme selected by users will be kept in localstorage.
const localStorageThemeString = localStorage.getItem(LOCAL_STORAGE_KEY_THEME);

if (localStorageThemeString) {
initialTheme = localStorageThemeString === 'dark' ? Mode.Dark : Mode.Light;
}
}

ReactDOM.render(
<React.StrictMode>
<ThemeProvider theme={initialTheme}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Header, { HeaderProps } from '@cloudscape-design/components/header';
import { FC, PropsWithChildren } from 'react';
import { useApplicationInfoContext } from '../../../contexts/ApplicationContext';

export interface ContentLayoutProps extends HeaderProps {
export interface ContentLayoutProps extends Omit<HeaderProps, 'info'> {
/**
* The title of the header.
*/
Expand All @@ -37,8 +37,9 @@ const ContentLayout: FC<PropsWithChildren<ContentLayoutProps>> = ({
header={<Header
variant="h1"
{...props}
info={applicationInfo.name ? `| ${applicationInfo.name}` : undefined}
>
{applicationInfo.name ? `${title} for: ${applicationInfo.name}` : title}
{title}
</Header>}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { applyDensity, applyMode, Density, Mode } from '@cloudscape-design/globa
import { FC, createContext, useState, useEffect, useContext, PropsWithChildren } from 'react';

import '@cloudscape-design/global-styles/index.css';
import useLocalStorageState from 'use-local-storage-state';

export interface ThemeProviderProps {
theme?: Mode;
Expand All @@ -39,12 +40,13 @@ const initialState: ThemeContextApi = {

const ThemeContext = createContext<ThemeContextApi>(initialState);


const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({
children,
...props
}) => {
const [theme, setTheme] = useState<Mode>(() => {
return props.theme || Mode.Light;
const [theme, setTheme] = useLocalStorageState<Mode>('theme', {
defaultValue: props.theme || Mode.Light,
});

const [density, setDensity] = useState<Density>(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useCallback, FC, PropsWithChildren, useEffect } from 'react';
import { useWorkspacesContext } from '../../../contexts';
import useExportImport, { PLACEHOLDER_EXCHANGE_DATA } from '../../../hooks/useExportImport';
import useRemoveData from '../../../hooks/useRemoveData';
import convertToMarkdown from '../../../utils/convertToMarkdown';

/**
* Export threat-composer functionalities via window object.
Expand All @@ -40,6 +41,11 @@ const WindowExporter: FC<PropsWithChildren<{}>> = ({ children }) => {
[importData],
);

const getCurrentWorkspaceDataMarkdown = useCallback(async () => {
return convertToMarkdown(getWorkspaceData());
}, [getWorkspaceData]);


useEffect(() => {
window.threatcomposer.getWorkspaceList = () => workspaceList;
}, [workspaceList]);
Expand All @@ -52,6 +58,10 @@ const WindowExporter: FC<PropsWithChildren<{}>> = ({ children }) => {
window.threatcomposer.getCurrentWorkspaceData = getWorkspaceData;
}, [getWorkspaceData]);

useEffect(() => {
window.threatcomposer.getCurrentWorkspaceDataMarkdown = getCurrentWorkspaceDataMarkdown;
}, [getCurrentWorkspaceDataMarkdown]);

useEffect(() => {
window.threatcomposer.setCurrentWorkspaceData = setWorkspaceData;
}, [setWorkspaceData]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,16 @@ import { css } from '@emotion/react';
import { FC, useEffect, useCallback, useState, ReactNode, PropsWithChildren, useMemo } from 'react';
import { DataExchangeFormat, HasContentDetails, ViewNavigationEvent } from '../../../../../customTypes';
import printStyles from '../../../../../styles/print';
import convertToMarkdown from '../../../../../utils/convertToMarkdown';
import convertToYaml from '../../../../../utils/convertToYaml';
import {
downloadContentAsMarkdown,
downloadContentAsWordDocx,
downloadContentAsYaml,
downloadObjectAsJson,
} from '../../../../../utils/downloadContent';
import sanitizeHtml from '../../../../../utils/sanitizeHtml';
import MarkdownViewer from '../../../../generic/MarkdownViewer';
import { getApplicationInfoContent } from '../../utils/getApplicationInfo';
import { getApplicationName } from '../../utils/getApplicationName';
import { getArchitectureContent } from '../../utils/getArchitecture';
import { getAssetsContent } from '../../utils/getAssets';
import { getAssumptionsContent } from '../../utils/getAssumptions';
import { getDataflowContent } from '../../utils/getDataFlow';
import { getMitigationsContent } from '../../utils/getMitigations';
import { getThreatsContent } from '../../utils/getThreats';


const styles = {
text: css({
Expand Down Expand Up @@ -112,18 +105,7 @@ const ThreatModelView: FC<ThreatModelViewProps> = ({
useEffect(() => {
const updateContent = async () => {
setLoading(true);
const sanitizedData = sanitizeHtml(data);
const processedContent = (composerMode === 'Full' ? [
(!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');

const processedContent = await convertToMarkdown(data, composerMode);
setContent(processedContent);
setLoading(false);
};
Expand Down Expand Up @@ -155,7 +137,6 @@ const ThreatModelView: FC<ThreatModelViewProps> = ({
downloadFileName && downloadContentAsYaml(yamlContent, downloadFileName);
}, [data, downloadFileName]);


const handleDownloadClick: CancelableEventHandler<ButtonDropdownProps.ItemClickDetails> = useCallback(async ({ detail }) => {
switch (detail.id) {
case 'docx':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { APP_MODE_IDE_EXTENSION } from '../../../configs/appMode';
import { useGlobalSetupContext, useWorkspacesContext } from '../../../contexts';
import { DataExchangeFormat, ViewNavigationEvent } from '../../../customTypes';
import useImportExport from '../../../hooks/useExportImport';
import useHasContent from '../../../hooks/useHasContent';
import getExportFileName from '../../../utils/getExportFileName';
import hasContent from '../../../utils/hasContent';

export interface ThreatModelProps extends ViewNavigationEvent {
onPrintButtonClick?: (data: DataExchangeFormat) => void;
Expand All @@ -34,13 +34,17 @@ const ThreatModel: FC<ThreatModelProps> = ({
}) => {
const { getWorkspaceData } = useImportExport();
const { composerMode, appMode } = useGlobalSetupContext();
const [_, hasContentDetails] = useHasContent();
const { currentWorkspace } = useWorkspacesContext();

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

const hasContentDetails = useMemo(() => {
const [, details] = hasContent(getWorkspaceData());
return details;
}, [getWorkspaceData]);

return (
<ThreatModelView
{...props}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ const ContentLayout: FC<PropsWithChildren<ContentLayoutProps & {
}

return (<ContentLayoutComponent
title='Threat statement list'
{...props}
>
{children}
Expand Down Expand Up @@ -370,18 +369,17 @@ const ThreatStatementList: FC<ThreatStatementListProps> = ({

return (<ContentLayout
composerMode={composerMode}
title='Threat statement list'
title='Threats'
actions={actions}
counter={`(${filteredStatementList.length})`}
info={composerMode === 'Full' ? undefined : <Button variant='icon' iconName='status-info' onClick={showInfoModal} />}
>
<SpaceBetween direction='vertical' size='s'>
<Container header={
composerMode === 'ThreatsOnly' ? <Header
actions={actions}
counter={`(${filteredStatementList.length})`}
info={<Button variant='icon' iconName='status-info' onClick={showInfoModal} />}
>Threat statement list</Header> : undefined}
>Threats</Header> : undefined}
>
<SpaceBetween direction='vertical' size='s'>
<TextFilter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
limitations under the License.
******************************************************************************************************************** */
import { FC } from 'react';
import useHasContent from '../../../hooks/useHasContent';
import useImportExport from '../../../hooks/useExportImport';
import hasContent from '../../../utils/hasContent';
import LandingPage, { LandingPageProps } from '../LandingPage';
import WorkspaceInsight, { WorkspaceInsightsProps } from '../WorkspaceInsights';

const WorkspaceHome: FC<WorkspaceInsightsProps & LandingPageProps> = (props) => {
const [hasContent] = useHasContent();
return hasContent ? <WorkspaceInsight {...props} /> : <LandingPage {...props} />;
const { getWorkspaceData } = useImportExport();
const [hasContentSum] = hasContent(getWorkspaceData());
return hasContentSum ? <WorkspaceInsight {...props} /> : <LandingPage {...props} />;
};

export default WorkspaceHome;
3 changes: 3 additions & 0 deletions packages/threat-composer/src/configs/localStorageKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ export const LOCAL_STORAGE_KEY_DATAFLOW_INFO = 'ThreatStatementGenerator.Dataflo

export const LOCAL_STORAGE_KEY_WORKSPACE_LIST_MIGRATION = 'ThreatStatementGenerator.workspaceListMigration';
export const LOCAL_STORAGE_KEY_THREATS_LIST_MIGRATION = 'ThreatStatementGenerator.threatListMigration';


export const LOCAL_STORAGE_KEY_THEME = 'ThreatComposer.theme';
14 changes: 10 additions & 4 deletions packages/threat-composer/src/contexts/GlobalSetupContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import useLocalStorageState from 'use-local-storage-state';
import { GlobalSetupContext, useGlobalSetupContext } from './context';
import { useThemeContext } from '../../components/generic/ThemeProvider';
import InfoModal from '../../components/global/InfoModal';
import { LOCAL_STORAGE_KEY_NEW_VISIT_FLAG } from '../../configs/localStorageKeys';
import { LOCAL_STORAGE_KEY_NEW_VISIT_FLAG, LOCAL_STORAGE_KEY_THEME } from '../../configs/localStorageKeys';
import { ComposerMode, AppMode } from '../../customTypes';
import EventController from '../../utils/EventController';

Expand Down Expand Up @@ -50,18 +50,24 @@ const GlobalSetupContextProvider: FC<PropsWithChildren<GlobalSetupContextProvide
features,
}) => {
const [fileImportModalVisible, setFileImportModalVisible] = useState(false);
const { setTheme, setDensity } = useThemeContext();
const { setTheme, setDensity, theme } = useThemeContext();

useEffect(() => {
window.threatcomposer.applyDensity = (density?: string) => {
setDensity(density === 'compact' ? Density.Compact : Density.Comfortable);
};

window.threatcomposer.applyTheme = (theme?: string) => {
setTheme(theme === 'dark' ? Mode.Dark : Mode.Light);
window.threatcomposer.applyTheme = (newTheme?: string) => {
setTheme(newTheme === 'dark' ? Mode.Dark : Mode.Light);
};
}, [setDensity, setTheme]);

useEffect(() => {
if (appMode !== 'ide-extension') {
window.localStorage.setItem(LOCAL_STORAGE_KEY_THEME, theme);
}
}, [appMode, theme]);

const [hasVisitBefore, setHasVisitBefore] = useLocalStorageState<boolean>(LOCAL_STORAGE_KEY_NEW_VISIT_FLAG, {
defaultValue: false,
});
Expand Down
1 change: 1 addition & 0 deletions packages/threat-composer/src/customTypes/dataExchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface ThreatComposerNamespace {
getWorkspaceList?: () => Workspace[];
getCurrentWorkspaceMetadata?: () => Workspace | null;
getCurrentWorkspaceData?: () => DataExchangeFormat;
getCurrentWorkspaceDataMarkdown?: () => Promise<string>;
stringifyWorkspaceData: (arg0: any) => string;
setCurrentWorkspaceData?: (arg0: DataExchangeFormat) => Promise<void>;
switchWorkspace?: (id: string | null) => void;
Expand Down
53 changes: 0 additions & 53 deletions packages/threat-composer/src/hooks/useHasContent/index.tsx

This file was deleted.

47 changes: 47 additions & 0 deletions packages/threat-composer/src/utils/convertToMarkdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/** *******************************************************************************************************************
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 { getApplicationInfoContent } from './utils/getApplicationInfo';
import { getApplicationName } from './utils/getApplicationName';
import { getArchitectureContent } from './utils/getArchitecture';
import { getAssetsContent } from './utils/getAssets';
import { getAssumptionsContent } from './utils/getAssumptions';
import { getDataflowContent } from './utils/getDataFlow';
import { getMitigationsContent } from './utils/getMitigations';
import { getThreatsContent } from './utils/getThreats';
import { DataExchangeFormat } from '../../customTypes';
import hasContent from '../hasContent';
import sanitizeHtml from '../sanitizeHtml';

const convertToMarkdown = async (data: DataExchangeFormat, composerMode = 'Full') => {
const sanitizedData = sanitizeHtml(data);
const [_, hasContentDetails] = hasContent(data);

const processedContent = (composerMode === 'Full' ? [
(!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');

return processedContent;
};

export default convertToMarkdown;
Loading

0 comments on commit 2de502e

Please sign in to comment.