# changes in the package. (i.e. example config files externally added
ITERATION?=1
-TARGETS?="centos7 debian8 debian9 debian10 ubuntu1404 ubuntu1604 ubuntu1804"
+TARGETS?="centos7 debian8 debian10 ubuntu1404 ubuntu1604 ubuntu1804 ubuntu2004"
DESCRIPTION=Arvados Workbench2 - Arvados is a free and open source platform for big data science.
MAINTAINER=Arvados Package Maintainers <packaging@arvados.org>
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export const sortByProperty = (propName: string) => (obj1: any, obj2: any) => {
+ const prop1 = obj1[propName];
+ const prop2 = obj2[propName];
+
+ if (prop1 > prop2) {
+ return 1;
+ }
+
+ if (prop1 < prop2) {
+ return -1;
+ }
+
+ return 0;
+};
const REDIRECT_TO_KEY = 'redirectTo';
export const storeRedirects = () => {
- if (window.location.href.indexOf(REDIRECT_TO_KEY) > -1) {
- const { location: { href }, localStorage } = window;
- const redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1];
+ let redirectUrl;
+ const { location: { href }, localStorage } = window;
- if (localStorage) {
- localStorage.setItem(REDIRECT_TO_KEY, redirectUrl);
- }
+ if (href.indexOf(REDIRECT_TO_KEY) > -1) {
+ redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1];
+ }
+
+ if (localStorage && redirectUrl) {
+ localStorage.setItem(REDIRECT_TO_KEY, redirectUrl);
}
};
if (localStorage && localStorage.getItem(REDIRECT_TO_KEY)) {
const redirectUrl = localStorage.getItem(REDIRECT_TO_KEY);
localStorage.removeItem(REDIRECT_TO_KEY);
+
if (redirectUrl) {
const sep = redirectUrl.indexOf("?") > -1 ? "&" : "?";
window.location.href = `${keepWebServiceUrl}${redirectUrl}${sep}api_token=${token}`;
import { Config } from "~/common/config";
import { uniqBy } from "lodash";
+export const TARGET_URL = 'targetURL';
export const API_TOKEN_KEY = 'apiToken';
export const USER_EMAIL_KEY = 'userEmail';
export const USER_FIRST_NAME_KEY = 'userFirstName';
}
}
+ public removeTargetURL() {
+ this.getStorage().removeItem(TARGET_URL);
+ }
+
+ public getTargetURL() {
+ return this.getStorage().getItem(TARGET_URL);
+ }
+
public removeApiToken() {
this.getStorage().removeItem(API_TOKEN_KEY);
}
this.getStorage().removeItem(USER_IS_ACTIVE);
this.getStorage().removeItem(USER_USERNAME);
this.getStorage().removeItem(USER_PREFS);
+ this.getStorage().removeItem(TARGET_URL);
}
public login(uuidPrefix: string, homeCluster: string, loginCluster: string, remoteHosts: { [key: string]: string }) {
const currentUrl = `${window.location.protocol}//${window.location.host}/token`;
const homeClusterHost = remoteHosts[homeCluster];
+ const rd = new URL(window.location.href);
+ this.getStorage().setItem(TARGET_URL, rd.pathname + rd.search);
window.location.assign(`https://${homeClusterHost}/login?${(uuidPrefix !== homeCluster && homeCluster !== loginCluster) ? "remote=" + uuidPrefix + "&" : ""}return_to=${currentUrl}`);
}
export const setSidePanelBreadcrumbs = (uuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const { treePicker } = getState();
+ const { treePicker, collectionPanel: { item } } = getState();
const breadcrumbs = getSidePanelTreeBreadcrumbs(uuid)(treePicker);
const path = getState().router.location!.pathname;
const currentUuid = path.split('/')[2];
const uuidKind = extractUuidKind(currentUuid);
if (uuidKind === ResourceKind.COLLECTION) {
- const collectionItem = await services.collectionService.get(currentUuid);
+ const collectionItem = item ? item : await services.collectionService.get(currentUuid);
dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
} else if (uuidKind === ResourceKind.PROCESS) {
const processItem = await services.containerRequestService.get(currentUuid);
const initialBreadcrumbs: ResourceBreadcrumb[] = [
{ label: category, uuid: category }
];
+ const { collectionPanel: { item } } = getState();
const path = getState().router.location!.pathname;
const currentUuid = path.split('/')[2];
const uuidKind = extractUuidKind(currentUuid);
: breadcrumbs,
initialBreadcrumbs);
if (uuidKind === ResourceKind.COLLECTION) {
- const collectionItem = await services.collectionService.get(currentUuid);
+ const collectionItem = item ? item : await services.collectionService.get(currentUuid);
dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
} else if (uuidKind === ResourceKind.PROCESS) {
const processItem = await services.containerRequestService.get(currentUuid);
export const loadCollectionPanel = (uuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const { collectionPanel: { item } } = getState();
dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid }));
- const collection = await services.collectionService.get(uuid);
+ const collection = item ? item : await services.collectionService.get(uuid);
dispatch(loadDetailsPanel(collection.uuid));
dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection }));
dispatch(resourcesActions.SET_RESOURCES([collection]));
//
// SPDX-License-Identifier: AGPL-3.0
-import { Dispatch } from 'redux';
+import * as copy from 'copy-to-clipboard';
import { ResourceKind } from '~/models/resource';
-import { unionize, ofType } from '~/common/unionize';
+import { getClipboardUrl } from '~/views-components/context-menu/actions/helpers';
-export const openInNewTabActions = unionize({
- COPY_STORE: ofType<{}>(),
- OPEN_COLLECTION_IN_NEW_TAB: ofType<string>(),
- OPEN_PROJECT_IN_NEW_TAB: ofType<string>()
-});
-
-export const openInNewTabAction = (resource: any) => (dispatch: Dispatch) => {
+const getUrl = (resource: any) => {
+ let url = null;
const { uuid, kind } = resource;
- dispatch(openInNewTabActions.COPY_STORE());
-
if (kind === ResourceKind.COLLECTION) {
- dispatch(openInNewTabActions.OPEN_COLLECTION_IN_NEW_TAB(uuid));
- } else if (kind === ResourceKind.PROJECT) {
- dispatch(openInNewTabActions.OPEN_PROJECT_IN_NEW_TAB(uuid));
+ url = `/collections/${uuid}`;
+ }
+ if (kind === ResourceKind.PROJECT) {
+ url = `/projects/${uuid}`;
+ }
+
+ return url;
+};
+
+export const openInNewTabAction = (resource: any) => () => {
+ const url = getUrl(resource);
+
+ if (url) {
+ window.open(`${window.location.origin}${url}`, '_blank');
+ }
+};
+
+export const copyToClipboardAction = (resource: any) => () => {
+ const url = getUrl(resource);
+
+ if (url) {
+ copy(getClipboardUrl(url, false));
}
};
\ No newline at end of file
collectionsContentAddress,
subprocessMiddleware,
];
+
const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares));
return createStore(rootReducer, enhancer);
}
{value === 4 && dialogContent(curlHeader, curlExample, classes)}
</DialogContent>
<DialogActions>
- <Button variant='text' color='primary' onClick={closeDialog}>
+ <Button data-cy="close-advanced-dialog" variant='text' color='primary' onClick={closeDialog}>
Close
</Button>
</DialogActions>
import { navigateToRootProject, navigateToLinkAccount } from "~/store/navigation/navigation-action";
import { Config } from "~/common/config";
import { getAccountLinkData } from "~/store/link-account-panel/link-account-panel-actions";
+import { replace } from "react-router-redux";
interface ApiTokenProps {
authService: AuthService;
const apiToken = getUrlParameter(search, 'api_token');
const loadMainApp = this.props.loadMainApp;
this.props.dispatch<any>(saveApiToken(apiToken)).finally(() => {
+ const redirectURL = this.props.authService.getTargetURL();
+
if (loadMainApp) {
- if (this.props.dispatch(getAccountLinkData())) {
+ if (redirectURL) {
+ this.props.authService.removeTargetURL();
+ this.props.dispatch(replace(redirectURL));
+ }
+ else if (this.props.dispatch(getAccountLinkData())) {
this.props.dispatch(navigateToLinkAccount);
}
else {
import { ContextMenuActionSet } from "../context-menu-action-set";
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "~/store/favorites/favorites-actions";
-import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon } from "~/components/icon/icon";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from "~/components/icon/icon";
import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
import { openSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions';
import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
export const readOnlyCollectionActionSet: ContextMenuActionSet = [[
{
component: ToggleFavoriteAction,
+ name: 'ToggleFavoriteAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleFavorite(resource)).then(() => {
dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
});
}
},
+ {
+ icon: OpenIcon,
+ name: "Open in new tab",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openInNewTabAction(resource));
+ }
+ },
+ {
+ icon: Link,
+ name: "Copy to clipboard",
+ execute: (dispatch, resource) => {
+ dispatch<any>(copyToClipboardAction(resource));
+ }
+ },
{
icon: CopyIcon,
name: "Make a copy",
},
{
component: ToggleTrashAction,
+ name: 'ToggleTrashAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
}
import { ContextMenuActionSet } from "../context-menu-action-set";
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "~/store/favorites/favorites-actions";
-import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon } from "~/components/icon/icon";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from "~/components/icon/icon";
import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
import { TogglePublicFavoriteAction } from "~/views-components/context-menu/actions/public-favorite-action";
import { publicFavoritePanelActions } from "~/store/public-favorites-panel/public-favorites-action";
import { togglePublicFavorite } from "~/store/public-favorites/public-favorites-actions";
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
export const collectionAdminActionSet: ContextMenuActionSet = [[
{
dispatch<any>(openCollectionUpdateDialog(resource));
}
},
+ {
+ icon: OpenIcon,
+ name: "Open in new tab",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openInNewTabAction(resource));
+ }
+ },
+ {
+ icon: Link,
+ name: "Copy to clipboard",
+ execute: (dispatch, resource) => {
+ dispatch<any>(copyToClipboardAction(resource));
+ }
+ },
{
icon: ShareIcon,
name: "Share",
},
{
component: ToggleFavoriteAction,
+ name: 'ToggleFavoriteAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleFavorite(resource)).then(() => {
dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
},
{
component: TogglePublicFavoriteAction,
+ name: 'TogglePublicFavoriteAction',
execute: (dispatch, resource) => {
dispatch<any>(togglePublicFavorite(resource)).then(() => {
dispatch<any>(publicFavoritePanelActions.REQUEST_ITEMS());
},
{
component: ToggleTrashAction,
+ name: 'ToggleTrashAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ContextMenuActionSet } from "../context-menu-action-set";
+import { ToggleFavoriteAction } from "../actions/favorite-action";
+import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
+import { toggleFavorite } from "~/store/favorites/favorites-actions";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon } from '~/components/icon/icon';
+import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
+import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
+import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
+import { openCollectionCopyDialog } from '~/store/collections/collection-copy-actions';
+import { toggleCollectionTrashed } from "~/store/trash/trash-actions";
+import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
+import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab';
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
+
+export const collectionResourceActionSet: ContextMenuActionSet = [[
+ {
+ icon: RenameIcon,
+ name: "Edit collection",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openCollectionUpdateDialog(resource));
+ }
+ },
+ {
+ icon: ShareIcon,
+ name: "Share",
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openSharingDialog(uuid));
+ }
+ },
+ {
+ component: ToggleFavoriteAction,
+ execute: (dispatch, resource) => {
+ dispatch<any>(toggleFavorite(resource)).then(() => {
+ dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
+ });
+ }
+ },
+ {
+ icon: OpenIcon,
+ name: "Open in new tab",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openInNewTabAction(resource));
+ }
+ },
+ {
+ icon: MoveToIcon,
+ name: "Move to",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openMoveCollectionDialog(resource));
+ }
+ },
+ {
+ icon: CopyIcon,
+ name: "Copy to project",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openCollectionCopyDialog(resource));
+ }
+ },
+ {
+ icon: DetailsIcon,
+ name: "View details",
+ execute: dispatch => {
+ dispatch<any>(toggleDetailsPanel());
+ }
+ },
+ {
+ icon: AdvancedIcon,
+ name: "Advanced",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openAdvancedTabDialog(resource.uuid));
+ }
+ },
+ {
+ component: ToggleTrashAction,
+ execute: (dispatch, resource) => {
+ dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
+ }
+ },
+ // {
+ // icon: RemoveIcon,
+ // name: "Remove",
+ // execute: (dispatch, resource) => {
+ // // add code
+ // }
+ // }
+]];
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
-import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon } from '~/components/icon/icon';
+import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from '~/components/icon/icon';
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "~/store/favorites/favorites-actions";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
export const readOnlyProjectActionSet: ContextMenuActionSet = [[
{
component: ToggleFavoriteAction,
+ name: 'ToggleFavoriteAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleFavorite(resource)).then(() => {
dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
});
}
},
+ {
+ icon: OpenIcon,
+ name: "Open in new tab",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openInNewTabAction(resource));
+ }
+ },
+ {
+ icon: Link,
+ name: "Copy to clipboard",
+ execute: (dispatch, resource) => {
+ dispatch<any>(copyToClipboardAction(resource));
+ }
+ },
{
icon: DetailsIcon,
name: "View details",
},
{
component: ToggleTrashAction,
+ name: 'ToggleTrashAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!));
}
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
-import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon } from '~/components/icon/icon';
+import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from '~/components/icon/icon';
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "~/store/favorites/favorites-actions";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
import { TogglePublicFavoriteAction } from "~/views-components/context-menu/actions/public-favorite-action";
import { togglePublicFavorite } from "~/store/public-favorites/public-favorites-actions";
import { publicFavoritePanelActions } from "~/store/public-favorites-panel/public-favorites-action";
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
export const projectAdminActionSet: ContextMenuActionSet = [[
{
dispatch<any>(openProjectCreateDialog(resource.uuid));
}
},
+ {
+ icon: OpenIcon,
+ name: "Open in new tab",
+ execute: (dispatch, resource) => {
+ dispatch<any>(openInNewTabAction(resource));
+ }
+ },
+ {
+ icon: Link,
+ name: "Copy to clipboard",
+ execute: (dispatch, resource) => {
+ dispatch<any>(copyToClipboardAction(resource));
+ }
+ },
{
icon: RenameIcon,
name: "Edit project",
},
{
component: ToggleFavoriteAction,
+ name: 'ToggleFavoriteAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleFavorite(resource)).then(() => {
dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
},
{
component: TogglePublicFavoriteAction,
+ name: 'TogglePublicFavoriteAction',
execute: (dispatch, resource) => {
dispatch<any>(togglePublicFavorite(resource)).then(() => {
dispatch<any>(publicFavoritePanelActions.REQUEST_ITEMS());
},
{
component: ToggleTrashAction,
+ name: 'ToggleTrashAction',
execute: (dispatch, resource) => {
dispatch<any>(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!));
}
//
// SPDX-License-Identifier: AGPL-3.0
-export const sanitizeToken = (href: string, tokenAsQueryParam: boolean = true): string => {
+export const sanitizeToken = (href: string, tokenAsQueryParam = true): string => {
const [prefix, suffix] = href.split('/t=');
const [token1, token2, token3, ...rest] = suffix.split('/');
const token = `${token1}/${token2}/${token3}`;
return `${[prefix, ...rest].join('/')}${tokenAsQueryParam ? `${sep}api_token=${token}` : ''}`;
};
-export const getClipboardUrl = (href: string): string => {
+export const getClipboardUrl = (href: string, shouldSanitizeToken = true): string => {
const { origin } = window.location;
- const url = sanitizeToken(href, false);
+ const url = shouldSanitizeToken ? sanitizeToken(href, false) : href;
- return `${origin}?redirectTo=${url}`;
+ return shouldSanitizeToken ? `${origin}?redirectTo=${url}` : `${origin}${url}`;
};
import { ContextMenuActionSet, ContextMenuAction } from "./context-menu-action-set";
import { Dispatch } from "redux";
import { memoize } from 'lodash';
+import { sortByProperty } from "~/common/array-utils";
type DataProps = Pick<ContextMenuProps, "anchorEl" | "items" | "open"> & { resource?: ContextMenuResource };
const mapStateToProps = (state: RootState): DataProps => {
const { open, position, resource } = state.contextMenu;
const menuActionSets = new Map<string, ContextMenuActionSet>();
export const addMenuActionSet = (name: string, itemSet: ContextMenuActionSet) => {
- menuActionSets.set(name, itemSet);
+ const sorted = itemSet.map(items => items.sort(sortByProperty('name')));
+ menuActionSets.set(name, sorted);
};
const emptyActionSet: ContextMenuActionSet = [];