Skip to content

Commit

Permalink
feat(ClientSideAPI): Provide standardized way for extensions and use…
Browse files Browse the repository at this point in the history
…rscript to interact (#55)

* chore: Add window exporter fondation

* feat(ClientSideAPI): Additional methods

* feat(ClientSideAPI): Iterate additional methods

* feat(ClientSideAPI): Iterate additional methods

* feat(ClientSideAPI): Iterate additional methods

* feat(ClientSideAPI): Iterate additional methods

* chore: Add local state storage support for workspace

* chore: Change workspace APIs

* chore: Change workspace APIs

* chore: Fix the issue where the workspace example switch not working

* fix: Fix an issue where record got duplicated during workspace rename

---------

Co-authored-by: Jessie Wei <[email protected]>
Co-authored-by: Darran Boyd <[email protected]>
  • Loading branch information
3 people authored Dec 11, 2023
1 parent 8b55e6f commit 716aac3
Show file tree
Hide file tree
Showing 24 changed files with 339 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/** *******************************************************************************************************************
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 { useCallback, FC, PropsWithChildren, useEffect } from 'react';
import { useWorkspacesContext } from '../../../contexts';
import { ThreatComposerNamespace } from '../../../customTypes/dataExchange';
import useExportImport, {
PLACEHOLDER_EXCHANGE_DATA,
PLACEHOLDER_EXCHANGE_DATA_FOR_WORKSPACE,
} from '../../../hooks/useExportImport';
import useRemoveData from '../../../hooks/useRemoveData';

declare global {
interface Window {
threatcomposer: ThreatComposerNamespace;
}
}

const stringifyWorkspaceData = (data: any) => {
return JSON.stringify(data, null, 2);
};

window.threatcomposer = {
getWorkspaceList: () => [PLACEHOLDER_EXCHANGE_DATA_FOR_WORKSPACE],
getCurrentWorkspaceMetadata: () => PLACEHOLDER_EXCHANGE_DATA_FOR_WORKSPACE,
getCurrentWorkspaceData: () => PLACEHOLDER_EXCHANGE_DATA,
stringifyWorkspaceData,
setCurrentWorkspaceData: () => Promise.resolve(),
switchWorkspace: () => {},
createWorkspace: () =>
Promise.resolve(PLACEHOLDER_EXCHANGE_DATA_FOR_WORKSPACE),
deleteWorkspace: () => Promise.resolve(),
renameWorkspace: () => Promise.resolve(),
};

/**
* Export threat-composer functionalities via window object.
*/
const WindowExporter: FC<PropsWithChildren<{}>> = ({ children }) => {
const { getWorkspaceData, parseImportedData, importData } = useExportImport();
const {
currentWorkspace,
workspaceList,
addWorkspace,
switchWorkspace,
renameWorkspace,
} = useWorkspacesContext();
const { deleteWorkspace } = useRemoveData();

const setWorkspaceData = useCallback(
async (data: any) => {
const parsedData = parseImportedData(data);
await importData(parsedData);
},
[importData],
);

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

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

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

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

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

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

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

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

return <>{children}</>;
};

export default WindowExporter;
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Input from '../../generic/Input';
export interface EditWorkspaceProps {
visible: boolean;
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
onConfirm: (workspace: string) => void;
onConfirm: (workspace: string) => Promise<void>;
value?: string;
editMode?: boolean;
}
Expand All @@ -42,8 +42,8 @@ const EditWorkspace: FC<EditWorkspaceProps> = ({
const inputRef= useRef<InputProps.Ref>();
const [value, setValue] = useState(props.value || '');

const handleConfirm = useCallback(() => {
onConfirm(value);
const handleConfirm = useCallback(async () => {
await onConfirm(value);
setVisible(false);
}, [onConfirm, value]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const WorkspaceSelector: FC<PropsWithChildren<WorkspaceSelectorProps>> = ({

const { workspaceExamples } = useWorkspaceExamplesContext();
const { importData, exportAll, exportSelectedThreats } = useImportExport();
const { removeData, deleteCurrentWorkspace } = useRemoveData();
const { removeData, deleteWorkspace } = useRemoveData();
const {
composerMode,
onPreview,
Expand Down Expand Up @@ -109,10 +109,7 @@ const WorkspaceSelector: FC<PropsWithChildren<WorkspaceSelectorProps>> = ({
if (selectedItem.value === DEFAULT_WORKSPACE_ID) {
switchWorkspace(null);
} else {
selectedItem.value && selectedItem.label && switchWorkspace({
id: selectedItem.value,
name: selectedItem.label,
});
selectedItem.value && selectedItem.label && switchWorkspace(selectedItem.value);
}
}, [switchWorkspace]);

Expand Down Expand Up @@ -147,7 +144,7 @@ const WorkspaceSelector: FC<PropsWithChildren<WorkspaceSelectorProps>> = ({
exportAll,
exportSelectedThreats,
filteredThreats,
deleteCurrentWorkspace,
deleteWorkspace,
currentWorkspace,
setRemoveDataModalVisible,
setRemoveWorkspaceModalVisible,
Expand All @@ -168,7 +165,7 @@ const WorkspaceSelector: FC<PropsWithChildren<WorkspaceSelectorProps>> = ({
setIsRemovingWorkspace(true);

try {
await deleteCurrentWorkspace(toDeleteWorkspaceId);
await deleteWorkspace(toDeleteWorkspaceId);
} catch (e) {
console.log('Error in deleting workspace', e);
} finally {
Expand All @@ -177,8 +174,8 @@ const WorkspaceSelector: FC<PropsWithChildren<WorkspaceSelectorProps>> = ({
}
}, []);

const handleImport = useCallback((data: DataExchangeFormat) => {
importData(data);
const handleImport = useCallback(async (data: DataExchangeFormat) => {
await importData(data);
onImported?.();
}, [importData, onImported]);

Expand Down Expand Up @@ -248,7 +245,9 @@ const WorkspaceSelector: FC<PropsWithChildren<WorkspaceSelectorProps>> = ({
{addWorkspaceModalVisible && <AddWorkspace
visible={addWorkspaceModalVisible}
setVisible={setAddWorkspaceModalVisible}
onConfirm={addWorkspace}
onConfirm={async (workspaceName: string) => {
await addWorkspace(workspaceName);
}}
/>}
{editWorkspaceModalVisible && currentWorkspace && <AddWorkspace
visible={editWorkspaceModalVisible}
Expand Down
3 changes: 3 additions & 0 deletions packages/threat-composer/src/configs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const IMAGE_BASE64_MAX_LENGTH = 1000000;
// Architecture diagram url, data flow diagram, url
export const IMAGE_URL_MAX_LENGTH = 2048;

export const STORAGE_LOCAL_STORAGE = 'LocalStorage';
export const STORAGE_LOCAL_STATE = 'LocalState';

export const ALL_LEVELS = 'All';
export const LEVEL_NOT_SET = '-';
export const LEVEL_HIGH = 'High';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import ApplicationLocalStateContextProvider from './components/LocalStateContext
import ApplicationLocalStorageContextProvider from './components/LocalStorageContextProvider';
import { useApplicationInfoContext } from './context';
import { ApplicationContextProviderProps } from './types';
import isWorkspaceExample from '../../utils/isWorkspaceExample';
import { useWorkspaceExamplesContext } from '../WorkspaceExamplesContext';
import { STORAGE_LOCAL_STATE } from '../../configs';
import useWorkspaceStorage from '../../hooks/useWorkspaceStorage';

const ApplicationContextProvider: FC<PropsWithChildren<ApplicationContextProviderProps>> = (props) => {
const { getWorkspaceExample } = useWorkspaceExamplesContext();
const { storageType, value } = useWorkspaceStorage(props.workspaceId);

return isWorkspaceExample(props.workspaceId) ?
return storageType === STORAGE_LOCAL_STATE ?
(<ApplicationLocalStateContextProvider
key={props.workspaceId}
initialValue={getWorkspaceExample(props.workspaceId)?.value.applicationInfo}
initialValue={value?.applicationInfo}
{...props} />) :
(<ApplicationLocalStorageContextProvider key={props.workspaceId} {...props} />);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import ArchitectureLocalStateContextProvider from './components/LocalStateContex
import ArchitectureLocalStorageContextProvider from './components/LocalStorageContextProvider';
import { useArchitectureInfoContext } from './context';
import { ArchitectureContextProviderProps } from './types';
import isWorkspaceExample from '../../utils/isWorkspaceExample';
import { useWorkspaceExamplesContext } from '../WorkspaceExamplesContext';
import { STORAGE_LOCAL_STATE } from '../../configs';
import useWorkspaceStorage from '../../hooks/useWorkspaceStorage';

const ArchitectureContextProvider: FC<PropsWithChildren<ArchitectureContextProviderProps>> = (props) => {
const { getWorkspaceExample } = useWorkspaceExamplesContext();
const { storageType, value } = useWorkspaceStorage(props.workspaceId);

return isWorkspaceExample(props.workspaceId) ?
return storageType === STORAGE_LOCAL_STATE ?
(<ArchitectureLocalStateContextProvider
key={props.workspaceId}
initialValue={getWorkspaceExample(props.workspaceId)?.value.architecture}
initialValue={value?.architecture}
{...props} />) :
(<ArchitectureLocalStorageContextProvider key={props.workspaceId} {...props} />);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import AssumptionLinksLocalStateContextProvider from './components/LocalStateCon
import AssumptionLinksLocalStorageContextProvider from './components/LocalStorageContextProvider';
import { useAssumptionLinksContext } from './context';
import { AssumptionLinksContextProviderProps } from './types';
import isWorkspaceExample from '../../utils/isWorkspaceExample';
import { useWorkspaceExamplesContext } from '../WorkspaceExamplesContext';
import { STORAGE_LOCAL_STATE } from '../../configs';
import useWorkspaceStorage from '../../hooks/useWorkspaceStorage';

const AssumptionLinksContextProvider: FC<PropsWithChildren<AssumptionLinksContextProviderProps>> = (props) => {
const { getWorkspaceExample } = useWorkspaceExamplesContext();
const { storageType, value } = useWorkspaceStorage(props.workspaceId);

return isWorkspaceExample(props.workspaceId) ?
return storageType === STORAGE_LOCAL_STATE ?
(<AssumptionLinksLocalStateContextProvider
key={props.workspaceId}
initialValue={getWorkspaceExample(props.workspaceId)?.value.assumptionLinks}
initialValue={value?.assumptionLinks}
{...props} />)
: (<AssumptionLinksLocalStorageContextProvider key={props.workspaceId} {...props} />);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import AssumptionsLocalStateContextProvider from './components/LocalStateContext
import AssumptionsLocalStorageContextProvider from './components/LocalStorageContextProvider';
import { useAssumptionsContext } from './context';
import { AssumptionsContextProviderProps } from './types';
import isWorkspaceExample from '../../utils/isWorkspaceExample';
import { useWorkspaceExamplesContext } from '../WorkspaceExamplesContext';
import { STORAGE_LOCAL_STATE } from '../../configs';
import useWorkspaceStorage from '../../hooks/useWorkspaceStorage';

const AssumptionsContextProvider: FC<PropsWithChildren<AssumptionsContextProviderProps>> = (props) => {
const { getWorkspaceExample } = useWorkspaceExamplesContext();
const { storageType, value } = useWorkspaceStorage(props.workspaceId);

return isWorkspaceExample(props.workspaceId) ?
return storageType === STORAGE_LOCAL_STATE ?
(<AssumptionsLocalStateContextProvider
key={props.workspaceId}
initialValue={getWorkspaceExample(props.workspaceId)?.value.assumptions}
initialValue={value?.assumptions}
{...props} />) :
(<AssumptionsLocalStorageContextProvider key={props.workspaceId} {...props} />);
};
Expand Down
23 changes: 13 additions & 10 deletions packages/threat-composer/src/contexts/ContextAggregator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { FC, PropsWithChildren } from 'react';
import { ComposerMode, DataExchangeFormat, ViewNavigationEvent } from '../../customTypes';
import GlobalSetupContextProvider from '../GlobalSetupContext';
import WorkspaceContextAggregator from '../WorkspaceContextAggregator';
import WorkspaceExamplesContext from '../WorkspaceExamplesContext';
import WorkspacesContextProvider, { WorkspacesContextProviderProps } from '../WorkspacesContext';

export interface ContextAggregatorProps extends ViewNavigationEvent {
Expand Down Expand Up @@ -48,16 +49,18 @@ const ContextAggregator: FC<PropsWithChildren<ContextAggregatorProps>> = ({
features={features}
onDefineWorkload={onDefineWorkload}
composerMode={composerMode}>
<WorkspacesContextProvider onWorkspaceChanged={onWorkspaceChanged} {...props}>
{(workspaceId) => (<WorkspaceContextAggregator
workspaceId={workspaceId}
requiredGlobalSetupContext={false}
onThreatEditorView={props.onThreatEditorView}
onThreatListView={props.onThreatListView}
>
{children}
</WorkspaceContextAggregator>)}
</WorkspacesContextProvider>
<WorkspaceExamplesContext>
<WorkspacesContextProvider onWorkspaceChanged={onWorkspaceChanged} {...props}>
{(workspaceId) => (<WorkspaceContextAggregator
workspaceId={workspaceId}
requiredGlobalSetupContext={false}
onThreatEditorView={props.onThreatEditorView}
onThreatListView={props.onThreatListView}
>
{children}
</WorkspaceContextAggregator>)}
</WorkspacesContextProvider>
</WorkspaceExamplesContext>
</GlobalSetupContextProvider>);
};

Expand Down
10 changes: 5 additions & 5 deletions packages/threat-composer/src/contexts/DataflowContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import DataflowLocalStateContextProvider from './components/LocalStateContextPro
import DataflowLocalStorageContextProvider from './components/LocalStorageContextProvider';
import { useDataflowInfoContext } from './context';
import { DataflowContextProviderProps } from './types';
import isWorkspaceExample from '../../utils/isWorkspaceExample';
import { useWorkspaceExamplesContext } from '../WorkspaceExamplesContext';
import { STORAGE_LOCAL_STATE } from '../../configs';
import useWorkspaceStorage from '../../hooks/useWorkspaceStorage';

const DataflowContextProvider: FC<PropsWithChildren<DataflowContextProviderProps>> = (props) => {
const { getWorkspaceExample } = useWorkspaceExamplesContext();
const { storageType, value } = useWorkspaceStorage(props.workspaceId);

return isWorkspaceExample(props.workspaceId) ?
return storageType === STORAGE_LOCAL_STATE ?
(<DataflowLocalStateContextProvider
key={props.workspaceId}
initialValue={getWorkspaceExample(props.workspaceId)?.value.dataflow}
initialValue={value?.dataflow}
{...props} />) :
(<DataflowLocalStorageContextProvider key={props.workspaceId} {...props} />);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import MitigationLinksLocalStateContextProvider from './components/LocalStateCon
import MitigationLinksLocalStorageContextProvider from './components/LocalStorageContextProvider';
import { useMitigationLinksContext } from './context';
import { MitigationLinksContextProviderProps } from './types';
import isWorkspaceExample from '../../utils/isWorkspaceExample';
import { useWorkspaceExamplesContext } from '../WorkspaceExamplesContext';
import { STORAGE_LOCAL_STATE } from '../../configs';
import useWorkspaceStorage from '../../hooks/useWorkspaceStorage';

const MitigationLinksContextProvider: FC<PropsWithChildren<MitigationLinksContextProviderProps>> = (props) => {
const { getWorkspaceExample } = useWorkspaceExamplesContext();
const { storageType, value } = useWorkspaceStorage(props.workspaceId);

return isWorkspaceExample(props.workspaceId) ?
return storageType === STORAGE_LOCAL_STATE ?
(<MitigationLinksLocalStateContextProvider
key={props.workspaceId}
initialValue={getWorkspaceExample(props.workspaceId)?.value.mitigationLinks}
initialValue={value?.mitigationLinks}
{...props} />) :
(<MitigationLinksLocalStorageContextProvider key={props.workspaceId} {...props} />);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import MitigationsLocalStateContextProvider from './components/LocalStateContext
import MitigationsLocalStorageContextProvider from './components/LocalStorageContextProvider';
import { useMitigationsContext } from './context';
import { MitigationsContextProviderProps } from './types';
import isWorkspaceExample from '../../utils/isWorkspaceExample';
import { useWorkspaceExamplesContext } from '../WorkspaceExamplesContext';
import { STORAGE_LOCAL_STATE } from '../../configs';
import useWorkspaceStorage from '../../hooks/useWorkspaceStorage';

const MitigationsContextProvider: FC<PropsWithChildren<MitigationsContextProviderProps>> = (props) => {
const { getWorkspaceExample } = useWorkspaceExamplesContext();
const { storageType, value } = useWorkspaceStorage(props.workspaceId);

return isWorkspaceExample(props.workspaceId) ?
return storageType === STORAGE_LOCAL_STATE ?
(<MitigationsLocalStateContextProvider
key={props.workspaceId}
initialValue={getWorkspaceExample(props.workspaceId)?.value.mitigations} {...props} />) :
initialValue={value?.mitigations} {...props} />) :
(<MitigationsLocalStorageContextProvider key={props.workspaceId} {...props} />);
};

Expand Down
Loading

0 comments on commit 716aac3

Please sign in to comment.