tools/arvados_config.yml
cypress/fixtures/files/5mb.bin
cypress/fixtures/files/cat.png
+cypress/fixtures/files/banner.html
+cypress/fixtures/files/tooltips.txt
cypress/fixtures/webdav-propfind-outputs.xml
.yarn/releases/*
package.json
--- /dev/null
+<div>
+ <h1>Hi there</h1>
+ <h3>This is my amazing</h3>
+ <h5 style="color: red">Banner</h5>
+</div>
\ No newline at end of file
--- /dev/null
+{
+ "[data-cy=side-panel-tree]": "This allows you to navigate through the app"
+}
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+describe('Collection panel tests', function () {
+ let activeUser;
+ let adminUser;
+ let collectionUUID;
+
+ before(function () {
+ // Only set up common users once. These aren't set up as aliases because
+ // aliases are cleaned up after every test. Also it doesn't make sense
+ // to set the same users on beforeEach() over and over again, so we
+ // separate a little from Cypress' 'Best Practices' here.
+ cy.getUser('admin', 'Admin', 'User', true, true)
+ .as('adminUser').then(function () {
+ adminUser = this.adminUser;
+ }
+ );
+ cy.getUser('collectionuser1', 'Collection', 'User', false, true)
+ .as('activeUser').then(function () {
+ activeUser = this.activeUser;
+ });
+ cy.on('uncaught:exception', (err, runnable) => {console.error(err)});
+ });
+
+ beforeEach(function () {
+ cy.clearCookies();
+ cy.clearLocalStorage();
+ });
+
+ it('should re-show the banner', () => {
+ setupTheEnvironment();
+
+ cy.loginAs(adminUser);
+
+ cy.wait(2000);
+
+ cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
+
+ cy.get('[title=Notifications]').click();
+ cy.get('li').contains('Restore Banner').click();
+
+ cy.wait(2000);
+
+ cy.get('[data-cy=confirmation-dialog-ok-btn]').should('be.visible');
+ });
+
+
+ it('should show tooltips and remove tooltips as localStorage key is present', () => {
+ setupTheEnvironment();
+
+ cy.loginAs(adminUser);
+
+ cy.wait(2000);
+
+ cy.get('[data-cy=side-panel-tree]').then(($el) => {
+ const el = $el.get(0) //native DOM element
+ expect(el._tippy).to.exist;
+ });
+
+ cy.wait(2000);
+
+ cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
+
+ cy.get('[title=Notifications]').click();
+ cy.get('li').contains('Disable tooltips').click();
+
+ cy.get('[data-cy=side-panel-tree]').then(($el) => {
+ const el = $el.get(0) //native DOM element
+ expect(el._tippy).to.be.undefined;
+ });
+ });
+
+ const setupTheEnvironment = () => {
+ cy.createCollection(adminUser.token, {
+ name: `BannerTooltipTest${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: adminUser.user.uuid,
+ }).as('bannerCollection');
+
+ cy.getAll('@bannerCollection')
+ .then(function ([bannerCollection]) {
+
+ collectionUUID=bannerCollection.uuid;
+
+ cy.loginAs(adminUser);
+
+ cy.goToPath(`/collections/${bannerCollection.uuid}`);
+
+ cy.get('[data-cy=upload-button]').click();
+
+ cy.fixture('files/banner.html').as('banner');
+ cy.fixture('files/tooltips.txt').as('tooltips');
+
+ cy.getAll('@banner', '@tooltips')
+ .then(([banner, tooltips]) => {
+ console.log(tooltips)
+ cy.get('[data-cy=drag-and-drop]').upload(banner, 'banner.html', false);
+ cy.get('[data-cy=drag-and-drop]').upload(tooltips, 'tooltips.json', false);
+ });
+
+ cy.get('[data-cy=form-submit-btn]').click();
+ cy.get('[data-cy=form-submit-btn]').should('not.exist');
+ cy.get('[data-cy=collection-files-right-panel]')
+ .contains('banner.html').should('exist');
+ cy.get('[data-cy=collection-files-right-panel]')
+ .contains('tooltips.json').should('exist');
+
+ cy.intercept({ method: 'GET', url: '**/arvados/v1/config?nocache=*' }, (req) => {
+ req.reply((res) => {
+ res.body.Workbench.BannerUUID = collectionUUID;
+ });
+ });
+ });
+ }
+});
{
prevSubject: 'element',
},
- (subject, file, fileName) => {
+ (subject, file, fileName, binaryMode = true) => {
cy.window().then(window => {
- const blob = b64toBlob(file, '', 512);
+ const blob = binaryMode
+ ? b64toBlob(file, '', 512)
+ : new Blob([file], {type: 'text/plain'});
const testFile = new window.File([blob], fileName);
cy.wrap(subject).trigger('drop', {
"set-value": "2.0.1",
"shell-escape": "^0.2.0",
"sinon": "7.3",
+ "tippy.js": "^6.3.7",
"tslint": "5.20.0",
"tslint-etc": "1.6.0",
"unionize": "2.1.2",
WebDAVDownload: { ExternalURL: '' },
WebShell: { ExternalURL: '' },
Workbench: {
- DisableSharingURLsUI: false,
- ArvadosDocsite: "",
- FileViewersConfigURL: "",
- WelcomePageHTML: "",
- InactivePageHTML: "",
- SSHHelpPageHTML: "",
- SSHHelpHostSuffix: "",
- SiteName: "",
- IdleTimeout: "0s"
- }
+ DisableSharingURLsUI: false,
+ ArvadosDocsite: "",
+ FileViewersConfigURL: "",
+ WelcomePageHTML: "",
+ InactivePageHTML: "",
+ SSHHelpPageHTML: "",
+ SSHHelpHostSuffix: "",
+ SiteName: "",
+ IdleTimeout: "0s"
+ },
},
Workbench: {
DisableSharingURLsUI: false,
import { Config } from 'common/config';
import { pluginConfig } from 'plugins';
import { MiddlewareListReducer } from 'common/plugintypes';
+import { tooltipsMiddleware } from './tooltips/tooltips-middleware';
import { sidePanelReducer } from './side-panel/side-panel-reducer'
import { bannerReducer } from './banner/banner-reducer';
routerMiddleware(history),
thunkMiddleware.withExtraArgument(services),
authMiddleware(services),
+ tooltipsMiddleware(services),
projectPanelMiddleware,
favoritePanelMiddleware,
allProcessessPanelMiddleware,
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { CollectionDirectory, CollectionFile } from "models/collection-file";
+import { Middleware, Store } from "redux";
+import { ServiceRepository } from "services/services";
+import { RootState } from "store/store";
+import tippy, { createSingleton } from 'tippy.js';
+import 'tippy.js/dist/tippy.css';
+
+let running = false;
+let tooltipsContents = null;
+let tooltipsFetchFailed = false;
+export const TOOLTIP_LOCAL_STORAGE_KEY = "TOOLTIP_LOCAL_STORAGE_KEY";
+
+const tippySingleton = createSingleton([], {delay: 10});
+
+export const tooltipsMiddleware = (services: ServiceRepository): Middleware => (store: Store) => next => action => {
+ const state: RootState = store.getState();
+
+ if (state && state.auth && state.auth.config && state.auth.config.clusterConfig && state.auth.config.clusterConfig.Workbench) {
+ const hideTooltip = localStorage.getItem(TOOLTIP_LOCAL_STORAGE_KEY);
+ const { BannerUUID: bannerUUID } = state.auth.config.clusterConfig.Workbench;
+
+ if (bannerUUID && !tooltipsContents && !hideTooltip && !tooltipsFetchFailed && !running) {
+ running = true;
+ fetchTooltips(services, bannerUUID);
+ } else if (tooltipsContents && !hideTooltip && !tooltipsFetchFailed) {
+ applyTooltips();
+ }
+ }
+
+ return next(action);
+};
+
+const fetchTooltips = (services, bannerUUID) => {
+ services.collectionService.files(bannerUUID)
+ .then(results => {
+ const tooltipsFile: CollectionDirectory | CollectionFile | undefined = results.find(({ name }) => name === 'tooltips.json');
+
+ if (tooltipsFile) {
+ running = true;
+ services.collectionService.getFileContents(tooltipsFile as CollectionFile)
+ .then(data => {
+ tooltipsContents = JSON.parse(data);
+ applyTooltips();
+ })
+ .catch(() => {})
+ .finally(() => {
+ running = false;
+ });
+ } else {
+ tooltipsFetchFailed = true;
+ }
+ })
+ .catch(() => {})
+ .finally(() => {
+ running = false;
+ });
+};
+
+const applyTooltips = () => {
+ const tippyInstances: any[] = Object.keys(tooltipsContents as any)
+ .map((key) => {
+ const content = (tooltipsContents as any)[key]
+ const element = document.querySelector(key);
+
+ if (element) {
+ const hasTippyAttatched = !!(element as any)._tippy;
+
+ if (!hasTippyAttatched && tooltipsContents) {
+ return tippy(element as any, { content });
+ }
+ }
+
+ return null;
+ })
+ .filter(data => !!data);
+
+ if (tippyInstances.length > 0) {
+ tippySingleton.setInstances(tippyInstances);
+ }
+};
\ No newline at end of file
import bannerActions from "store/banner/banner-action";
import { BANNER_LOCAL_STORAGE_KEY } from "views-components/baner/banner";
import { RootState } from "store/store";
+import { TOOLTIP_LOCAL_STORAGE_KEY } from "store/tooltips/tooltips-middleware";
+import { useCallback } from "react";
const mapStateToProps = (state: RootState): NotificationsMenuProps => ({
isOpen: state.banner.isOpen,
export const NotificationsMenuComponent = (props: NotificationsMenuComponentProps) => {
const { isOpen, openBanner } = props;
- const result = localStorage.getItem(BANNER_LOCAL_STORAGE_KEY);
+ const bannerResult = localStorage.getItem(BANNER_LOCAL_STORAGE_KEY);
+ const tooltipResult = localStorage.getItem(TOOLTIP_LOCAL_STORAGE_KEY);
const menuItems: any[] = [];
- if (!isOpen && result) {
+ if (!isOpen && bannerResult) {
menuItems.push(<MenuItem><span onClick={openBanner}>Restore Banner</span></MenuItem>);
}
+ const toggleTooltips = useCallback(() => {
+ if (tooltipResult) {
+ localStorage.removeItem(TOOLTIP_LOCAL_STORAGE_KEY);
+ } else {
+ localStorage.setItem(TOOLTIP_LOCAL_STORAGE_KEY, 'true');
+ }
+ window.location.reload();
+ }, [tooltipResult]);
+
+ if (tooltipResult) {
+ menuItems.push(<MenuItem><span onClick={toggleTooltips}>Enable tooltips</span></MenuItem>);
+ } else {
+ menuItems.push(<MenuItem><span onClick={toggleTooltips}>Disable tooltips</span></MenuItem>);
+ }
+
if (menuItems.length === 0) {
menuItems.push(<MenuItem>You are up to date</MenuItem>);
}
id="account-menu"
title="Notifications">
{
- menuItems.map(item => item)
+ menuItems.map((item, i) => <div key={i}>{item}</div>)
}
</DropdownMenu>);
}
languageName: node
linkType: hard
+"@popperjs/core@npm:^2.9.0":
+ version: 2.11.6
+ resolution: "@popperjs/core@npm:2.11.6"
+ checksum: 47fb328cec1924559d759b48235c78574f2d71a8a6c4c03edb6de5d7074078371633b91e39bbf3f901b32aa8af9b9d8f82834856d2f5737a23475036b16817f0
+ languageName: node
+ linkType: hard
+
"@samverschueren/stream-to-observable@npm:^0.3.0":
version: 0.3.1
resolution: "@samverschueren/stream-to-observable@npm:0.3.1"
set-value: 2.0.1
shell-escape: ^0.2.0
sinon: 7.3
+ tippy.js: ^6.3.7
ts-mock-imports: 1.3.7
tslint: 5.20.0
tslint-etc: 1.6.0
languageName: node
linkType: hard
+"tippy.js@npm:^6.3.7":
+ version: 6.3.7
+ resolution: "tippy.js@npm:6.3.7"
+ dependencies:
+ "@popperjs/core": ^2.9.0
+ checksum: cac955318a65288e8d2dca05059878b003c6e66f92c94f7810f5bc5448eb6646abdf7dacc9bd00020e2611592598d0aae3a28ec9a45349a159603c3fdddce5fb
+ languageName: node
+ linkType: hard
+
"tmp@npm:^0.0.33":
version: 0.0.33
resolution: "tmp@npm:0.0.33"