Skip to content

Commit

Permalink
Add copy narrative function.
Browse files Browse the repository at this point in the history
  • Loading branch information
dakotablair committed Sep 14, 2023
1 parent 1c579bf commit 8424342
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 33 deletions.
34 changes: 23 additions & 11 deletions src/common/api/narrativeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,41 @@ const narrativeService = dynamicService({
});

export interface NarrativeServiceParams {
copyNarrative: { nameNew: string; workspaceRef: string; workspaceId: number };
getStatus: void;
renameNarrative: { nameNew: string; narrativeRef: string };
restoreNarrative: { objId: number; version: number; wsId: number };
}

interface NarrativeServiceResults {
copyNarrative: unknown;
getStatus: { state: string }[];
renameNarrative: unknown;
restoreNarrative: unknown;
}

export const narrativeServiceApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
copyNarrative: builder.mutation<
NarrativeServiceResults['copyNarrative'],
NarrativeServiceParams['copyNarrative']
>({
query: ({ nameNew, workspaceRef, workspaceId }) =>
narrativeService({
method: 'NarrativeService.copy_narrative',
params: [{ newName: nameNew, workspaceRef, workspaceId }],
}),
}),
getStatus: builder.query<
NarrativeServiceResults['getStatus'],
NarrativeServiceParams['getStatus']
>({
query: () =>
narrativeService({
method: 'NarrativeService.status',
params: [],
}),
}),
renameNarrative: builder.mutation<
NarrativeServiceResults['renameNarrative'],
NarrativeServiceParams['renameNarrative']
Expand All @@ -41,18 +63,8 @@ export const narrativeServiceApi = baseApi.injectEndpoints({
params: [{ ver: version, objid: objId, wsid: wsId }],
}),
}),
getStatus: builder.query<
NarrativeServiceResults['getStatus'],
NarrativeServiceParams['getStatus']
>({
query: () =>
narrativeService({
method: 'NarrativeService.status',
params: [],
}),
}),
}),
});

export const { getStatus, renameNarrative, restoreNarrative } =
export const { copyNarrative, getStatus, renameNarrative, restoreNarrative } =
narrativeServiceApi.endpoints;
61 changes: 52 additions & 9 deletions src/features/navigator/NarrativeControl/Copy.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
/* NarrativeControl/Copy */
import { FC } from 'react';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { isKBaseBaseQueryError } from '../../../common/api/utils/common';
import { parseError } from '../../../common/api/utils/parseError';
import { copyNarrative } from '../../../common/api/narrativeService';
import { Button } from '../../../common/components';
import {
inputRegisterFactory,
MAX_WS_METADATA_VALUE_SIZE,
} from '../../../common/components/Input.common';
import { Input } from '../../../common/components/Input';
import { useAppDispatch } from '../../../common/hooks';
import { TODOAddLoadingState } from '../common';
import { useNarrativeServiceStatus } from '../hooks';
import { copyNarrative } from '../navigatorSlice';
import { ControlProps } from './common';
import { copyNarrative as copyAction, setLoading } from '../navigatorSlice';
import { ControlProps, ErrorMessage } from './common';

export interface CopyValues {
narrativeCopyName: string;
Expand All @@ -22,24 +24,50 @@ export interface CopyProps extends ControlProps {
}

export const Copy: FC<CopyProps> = ({ narrativeDoc, modalClose, version }) => {
/* hooks */
const dispatch = useAppDispatch();
useNarrativeServiceStatus();
const { formState, getValues, register } = useForm<CopyValues>({
defaultValues: {
narrativeCopyName: `${narrativeDoc.narrative_title} - Copy`,
},
mode: 'all',
});
const [copyTrigger] = copyNarrative.useMutation();
/* derived values */
const inputRegister = inputRegisterFactory<CopyValues>({
formState,
register,
});
const { access_group: wsId, obj_id: objId } = narrativeDoc;
const errors = formState.errors;
const errorEntries = Object.entries(errors);
const formInvalid = errorEntries.length > 0;
/* copy narrative callback */
const copyNarrativeHandler = async () => {
const { narrativeCopyName: name } = getValues();
await TODOAddLoadingState();
dispatch(copyNarrative({ wsId: narrativeDoc.access_group, name, version }));
const message = `Copy ${wsId}/${objId}/${version} as ${name}.`;
modalClose();
dispatch(copyAction({ wsId: narrativeDoc.access_group, name, version }));
try {
await copyTrigger({
nameNew: name,
workspaceRef: `${wsId}/${objId}/${version}`,
workspaceId: wsId,
}).unwrap();
dispatch(setLoading(false));
} catch (err) {
if (!isKBaseBaseQueryError(err)) {
console.error({ err }); // eslint-disable-line no-console
toast(ErrorMessage({ err }));
return;
}
toast(ErrorMessage({ err: parseError(err) }));
dispatch(setLoading(false));
return;
}
toast(message);
};
/* Copy component */
return (
<>
<p>
Expand All @@ -49,16 +77,31 @@ export const Copy: FC<CopyProps> = ({ narrativeDoc, modalClose, version }) => {
</p>
<p>Enter a name for the new Narrative.</p>
<div>
{formInvalid ? (
<>
Errors:
<ul>
{Object.entries(errors).map(([name, err]) => (
<li key={name}>{err.message}</li>
))}
</ul>
</>
) : (
<></>
)}
<Input
label={<>New Narrative Title</>}
maxLength={MAX_WS_METADATA_VALUE_SIZE}
{...inputRegister('narrativeCopyName', {
maxLength: {
value: MAX_WS_METADATA_VALUE_SIZE,
message: 'too long',
message: 'The selected name is too long.',
},
})}
/>
<Button onClick={copyNarrativeHandler}>OK</Button>
<Button disabled={formInvalid} onClick={copyNarrativeHandler}>
OK
</Button>
<Button onClick={modalClose}>Cancel</Button>
</div>
</>
Expand Down
8 changes: 6 additions & 2 deletions src/features/navigator/NarrativeControl/Delete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@ import { deleteNarrative, loading, setLoading } from '../navigatorSlice';
import { ControlProps, ErrorMessage } from './common';

export const Delete: FC<ControlProps> = ({ narrativeDoc, modalClose }) => {
/* hooks */
const dispatch = useAppDispatch();
const loadState = useAppSelector(loading);
const params = useAppSelector(getParams);
const navigate = useNavigate();
const [userConfirmation, setUserConfirmation] = useState(false);
const [deleteTrigger] = deleteWorkspace.useMutation();

const wsId = narrativeDoc.access_group;
useEffect(() => {
if (loadState) return;
if (!userConfirmation) return;
});

/* derived values */
const wsId = narrativeDoc.access_group;
const message = `Deleted narrative ${wsId}.`;

/* delete narrative callback */
const deleteNarrativeHandler = async () => {
setUserConfirmation(true);
modalClose();
Expand All @@ -49,6 +52,7 @@ export const Delete: FC<ControlProps> = ({ narrativeDoc, modalClose }) => {
toast(message);
navigate(generatePathWithSearchParams('/narratives', params));
};
/* Delete component */
return (
<>
<p>Delete Narrative?</p>
Expand Down
1 change: 1 addition & 0 deletions src/features/navigator/NarrativeControl/Rename.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const Rename: FC<{
}
toast(message);
};
/* Rename component */
return (
<>
<p>Rename Narrative</p>
Expand Down
18 changes: 11 additions & 7 deletions src/features/navigator/Navigator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,17 @@ describe('The <Navigator /> component...', () => {
testStore.dispatch(baseApi.util.resetApiState());
});

test('renders.', () => {
const { container } = render(
<Provider store={createTestStore()}>
<Router>
<Navigator />
</Router>
</Provider>
test('renders.', async () => {
const { container } = await waitFor(() =>
render(
<Provider store={createTestStore()}>
<Router>
<ErrorBoundary FallbackComponent={TestingError} onError={logError}>
<Navigator />
</ErrorBoundary>
</Router>
</Provider>
)
);
expect(container).toBeTruthy();
expect(container.querySelector('section.navigator')).toBeInTheDocument();
Expand Down
9 changes: 8 additions & 1 deletion src/features/navigator/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import {
normalizeVersion,
searchParams,
} from './common';
import { useNarratives } from './hooks';
import {
useNarratives,
//useNarrativeServiceStatus // See below
} from './hooks';
import {
loading,
navigatorSelected,
Expand Down Expand Up @@ -210,6 +213,10 @@ const Navigator: FC = () => {
term: search,
username,
});
/* This causes tests to hang which means there is probably a bug in the way
dynamic services are handled.
// useNarrativeServiceStatus();
*/
const items = useAppSelector(narrativeDocs);
const narrativeSelected = getNarrativeSelected({ id, obj, verRaw, items });
// hooks that update state
Expand Down
6 changes: 3 additions & 3 deletions src/features/navigator/navigatorSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ export const navigatorSlice = createSlice({
state,
action: PayloadAction<{ name: string; version: number; wsId: number }>
) => {
const { name, version, wsId } = action.payload;
const message = `Copy version ${version} of ${wsId} with name ${name}.`;
console.log(message); // eslint-disable-line no-console
// For now, wait until the page refreshes to reflect the changes.
state.synchronizedLast = Date.now();
state.synchronized = false;
},
deleteNarrative: (state, action: PayloadAction<{ wsId: number }>) => {
const { wsId } = action.payload;
Expand Down

0 comments on commit 8424342

Please sign in to comment.