Skip to content

Commit

Permalink
Merge pull request #97 from kbase/add_object_reverter
Browse files Browse the repository at this point in the history
Add object reverter
  • Loading branch information
charleshtrenholm authored Dec 9, 2021
2 parents 33ea5f7 + 47d83f3 commit 95807ba
Show file tree
Hide file tree
Showing 9 changed files with 544 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import CopyItem from './CopyItem';
import LinkOrgItem from './LinkOrgItem';
import RenameItem from './RenameItem';
import SharingItem from './sharing/SharingItem';
import RevertNarrative from './RevertNarrative';

interface State {
showMenu: boolean;
showModal: boolean;
modalItem: MenuItem | null;
}

interface MenuItem {
export interface MenuItem {
title: string;
icon: string;
dialogTitle?: string;
Expand Down Expand Up @@ -55,6 +56,23 @@ const menuItems: Array<MenuItem> = [
},
];

// options to be displayed when user has selected an older version
// TODO: use Rename option for potential "version flagging" feature of older versions
const oldVersionMenuItems: Array<MenuItem> = [
{
title: 'Copy this Version',
icon: 'fa fa-copy',
dialogTitle: 'Make a copy of this version',
menuComponent: CopyItem,
},
{
title: 'Restore Version',
icon: 'fa fa-history',
dialogTitle: 'Revert narrative to this version',
menuComponent: RevertNarrative,
},
];

export default class ControlMenu extends Component<
ControlMenuItemProps,
State
Expand Down Expand Up @@ -110,14 +128,17 @@ export default class ControlMenu extends Component<
}

render() {
const { isCurrentVersion, narrative } = this.props;
let menu = null;
if (this.state.showMenu) {
menu = (
<div
className="ba b--black-30 bg-white db fr absolute"
style={{ top: '3em', right: '1em', boxShadow: '0 2px 3px #aaa' }}
>
{menuItems.map((item, idx) => this.menuItem(item, idx))}
{isCurrentVersion
? menuItems.map((item, idx) => this.menuItem(item, idx))
: oldVersionMenuItems.map((item, idx) => this.menuItem(item, idx))}
</div>
);
}
Expand All @@ -140,11 +161,22 @@ export default class ControlMenu extends Component<

return (
<div className="cursor tr">
<span
className="black-20 dim fa fa-2x fa-cog"
style={{ cursor: 'pointer' }}
onClick={(e) => this.menuClicked()}
></span>
{isCurrentVersion ? (
<span
className="black-20 dim fa fa-2x fa-cog"
style={{ cursor: 'pointer' }}
onClick={(e) => this.menuClicked()}
></span>
) : (
<button
className="button"
style={{ border: 'none' }}
onClick={(e) => this.menuClicked()}
>
v{narrative.version} Options
<i className="fa fa-caret-down ml2"></i>
</button>
)}
{menu}
{modal}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Doc } from '../../../../utils/narrativeData';
import { History } from 'history';

interface ControlMenuItemProps {
narrative: Doc;
cancelFn?: () => void;
doneFn: () => void;
isCurrentVersion?: boolean;
history?: History;
category?: string;
}

export default ControlMenuItemProps;
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import React, { Component } from 'react';
import { getCurrentUserPermission } from '../../../../utils/narrativeData';
import ControlMenuItemProps from './ControlMenuItemProps';
import DashboardButton from '../../../generic/DashboardButton';
import { LoadingSpinner } from '../../../generic/LoadingSpinner';
import Runtime from '../../../../utils/runtime';
import {
KBaseServiceClient,
KBaseDynamicServiceClient,
} from '@kbase/narrative-utils';
import { RouteComponentProps, withRouter } from 'react-router';
import ControlMenu from './ControlMenu';

type ComponentStatus =
| 'none'
| 'loading'
| 'ready'
| 'reverting'
| 'error'
| 'success';

interface ComponentStateBase {
status: ComponentStatus;
newVersion?: number; // the newest version returned from narrative service after successful revert
}

interface ComponentStateNone extends ComponentStateBase {
status: 'none';
}

interface ComponentStateLoading extends ComponentStateBase {
status: 'loading';
}

interface ComponentStateReady extends ComponentStateBase {
status: 'ready';
}

interface ComponentStateReverting extends ComponentStateBase {
status: 'reverting';
}

interface ComponentStateError extends ComponentStateBase {
status: 'error';
error: {
message: string | Array<string>;
};
}

interface ComponentStateSuccess extends ComponentStateBase {
status: 'success';
}

type ComponentState =
| ComponentStateNone
| ComponentStateLoading
| ComponentStateReady
| ComponentStateReverting
| ComponentStateError
| ComponentStateSuccess;
class RevertNarrative extends Component<ControlMenuItemProps, ComponentState> {
constructor(props: ControlMenuItemProps) {
super(props);
this.state = {
status: 'none',
};
}

async componentDidMount() {
this.setState({
status: 'loading',
});
try {
const perm = await getCurrentUserPermission(
this.props.narrative.access_group
);
if (perm === 'a') {
this.setState({
status: 'ready',
});
} else {
this.setState({
status: 'error',
error: {
message: 'You do not have permission to revert this Narrative.',
},
});
}
} catch (e) {
console.error(e);
}
}

async doRevert() {
this.setState({
status: 'reverting',
});

const { narrative } = this.props;
const narrativeClient = new KBaseDynamicServiceClient({
module: 'NarrativeService',
version: 'dev',
authToken: Runtime.token(),
});

try {
const revertResult = await narrativeClient.call(
'revert_narrative_object',
[
{
wsid: narrative.access_group,
objid: narrative.obj_id,
ver: narrative.version,
},
]
);
this.setState({
status: 'success',
newVersion: revertResult[4],
});
} catch (error) {
const message = (() => {
if (error instanceof Error) {
return error.message;
}
return 'Unknown error';
})();

this.setState({
status: 'error',
error: {
message,
},
});
}
}

renderError({ error: { message } }: ComponentStateError) {
const messageContent = (() => {
if (typeof message === 'string') {
return <p>{message}</p>;
} else {
return message.map((message, index) => {
return <p key={index}>{message}</p>;
});
}
})();
const done = () => {
this.props.doneFn();
if (this.props.cancelFn) {
this.props.cancelFn();
}
};
return (
<>
<div style={{ fontWeight: 'bold', color: 'red' }}>Error</div>
{messageContent}
<div style={{ textAlign: 'center' }}>
<DashboardButton onClick={done}>Close</DashboardButton>
</div>
</>
);
}

renderLoading(message: string) {
return (
<div style={{ textAlign: 'center' }}>
<LoadingSpinner loading={true} />
</div>
);
}

renderSuccess() {
const done = () => {
this.props.doneFn();
if (this.props.cancelFn) {
this.props.cancelFn();
}
if (this.props.history) {
const { access_group, obj_id } = this.props.narrative;
const newUpa = `${access_group}/${obj_id}/${this.state.newVersion}`;
const { category } = this.props;
const queryParams = new URLSearchParams(location.search);
const prefix = '/' + (category === 'own' ? '' : `${category}/`);
const newLocation = `${prefix}${newUpa}?${queryParams.toString()}`;
this.props.history.push(newLocation);
}
};
return (
<div style={{ textAlign: 'center' }}>
<p>
The Narrative has been successfully reverted to version{' '}
{this.props.narrative.version}.
</p>
<p>
It may take up to 30 seconds for this to be reflected in the display.
</p>
<DashboardButton onClick={done}>Close</DashboardButton>
</div>
);
}

renderConfirmation() {
return (
<>
<div className="pb2">
<p>
Reverting a narrative to a previous version is permanent and may
cause data loss.
</p>
<p style={{ fontWeight: 'bold' }}>This action cannot be undone!</p>
</div>
<div className="pb2">Continue?</div>
<div>
<DashboardButton
onClick={() => this.doRevert()}
bgcolor={'red'}
textcolor={'white'}
>
Revert
</DashboardButton>
<DashboardButton onClick={this.props.cancelFn}>
Cancel
</DashboardButton>
</div>
</>
);
}

render() {
switch (this.state.status) {
case 'none':
case 'loading':
return this.renderLoading('Loading');
case 'ready':
return this.renderConfirmation();
case 'reverting':
return this.renderLoading('Reverting Narrative');
case 'success':
return this.renderSuccess();
case 'error':
return this.renderError(this.state);
}
}
}

export default withRouter<ControlMenuItemProps & RouteComponentProps, any>(
RevertNarrative
);
Loading

0 comments on commit 95807ba

Please sign in to comment.