diff --git a/backend/metagrid/api_proxy/views.py b/backend/metagrid/api_proxy/views.py index 7f419972e..331d26396 100644 --- a/backend/metagrid/api_proxy/views.py +++ b/backend/metagrid/api_proxy/views.py @@ -202,7 +202,7 @@ def do_request(request, urlbase): resp = requests.get(urlbase) # print(resp.text) - httpresp = HttpResponse(resp.text) + httpresp = HttpResponse(resp.text, content_type="text/json") httpresp.status_code = resp.status_code return httpresp diff --git a/frontend/.envs/.react b/frontend/.envs/.react index 34ba361b3..64f0af1c3 100644 --- a/frontend/.envs/.react +++ b/frontend/.envs/.react @@ -13,7 +13,7 @@ REACT_APP_AUTHENTICATION_METHOD=globus # Globus # ------------------------------------------------------------------------------ REACT_APP_GLOBUS_REDIRECT=http://localhost:3000/cart/items -REACT_APP_CLIENT_ID=7fa7ac4a-a051-4b26-836f-b292b5c5b268 +REACT_APP_CLIENT_ID=8286db4d-269e-4c68-88a1-2af9a20f7f09 REACT_APP_GLOBUS_NODES=aims3.llnl.gov,esgf-data1.llnl.gov,esgf-data2.llnl.gov # ------------------------------------------------------------------------------ diff --git a/frontend/package.json b/frontend/package.json index 843415c0b..0f4920a12 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "1.2.0", + "version": "1.2.1", "private": true, "scripts": { "build:local": "env-cmd -f .envs/.react react-scripts build", @@ -59,7 +59,7 @@ } }, "moduleNameMapper": { - "react-markdown": "/node_modules/react-markdown/react-markdown.min.js" + "react-markdown": "/src/test/__mocks__/ReactMarkdownMock.tsx" } }, "dependencies": { @@ -83,7 +83,7 @@ "react-dom": "18.2.0", "react-hotjar": "2.2.1", "react-joyride": "2.5.3", - "react-markdown": "8.0.7", + "react-markdown": "9.0.1", "react-router-dom": "^6.9.0", "react-scripts": "5.0.1", "recoil": "0.7.7", diff --git a/frontend/public/changelog/v1.2.1.md b/frontend/public/changelog/v1.2.1.md new file mode 100644 index 000000000..f6f7e41c5 --- /dev/null +++ b/frontend/public/changelog/v1.2.1.md @@ -0,0 +1,4 @@ +## Summary + +1. Updates to the tutorials: added new tutorial for the Globus transfer feature. +2. Minor bugfixes. diff --git a/frontend/src/common/TargetObject.test.ts b/frontend/src/common/TargetObject.test.ts index d5e6dbee3..ae361d8fe 100644 --- a/frontend/src/common/TargetObject.test.ts +++ b/frontend/src/common/TargetObject.test.ts @@ -30,7 +30,7 @@ describe('Test TourTarget class', () => { createTestTour('testGroup', {}); }); - it('Successfully creates joyride tour fron test targets', () => { + it('Successfully creates joyride tour from test targets', () => { const testTargets = { test1: new TargetObject('test1'), test2: new TargetObject('test2'), diff --git a/frontend/src/common/reactJoyrideSteps.ts b/frontend/src/common/reactJoyrideSteps.ts index 070094c34..df1233770 100644 --- a/frontend/src/common/reactJoyrideSteps.ts +++ b/frontend/src/common/reactJoyrideSteps.ts @@ -157,9 +157,21 @@ export const cartTourTargets = { libraryBtn: new TargetObject(), downloadAllType: new TargetObject(), downloadAllBtn: new TargetObject(), + globusCollectionDropdown: new TargetObject(), removeItemsBtn: new TargetObject(), }; +export const manageCollectionsTourTargets = { + globusCollectionsForm: new TargetObject(), + searchCollectionInput: new TargetObject(), + globusSearchResultsPanel: new TargetObject(), + globusSearchResults: new TargetObject(), + mySavedCollectionsPanel: new TargetObject(), + mySavedCollections: new TargetObject(), + saveCollectionBtn: new TargetObject(), + cancelCollectionBtn: new TargetObject(), +}; + export const savedSearchTourTargets = { savedSearches: new TargetObject(), projectDescription: new TargetObject(), @@ -181,6 +193,7 @@ export const nodeTourTargets = { export enum TourTitles { Main = 'Main Search Page Tour', Cart = 'Data Cart Tour', + ManageCollections = 'Manage My Collections Tour', Searches = 'Saved Searches Tour', Node = 'Node Status Tour', Welcome = 'Welcome Tour', @@ -713,14 +726,29 @@ export const createCartItemsTour = ( await delay(300); } ) + .addNextStep( + cartTourTargets.removeItemsBtn.selector(), + 'We can remove all items from the cart with this button.', + 'right-start' + ) .addNextStep( cartTourTargets.downloadAllType.selector(), - 'This will select which download script to use (only wget is available currently).', + 'This will select which download method to use. The Globus download method is the default.', + 'top-start', + /* istanbul ignore next */ + async () => { + clickFirstElement(cartTourTargets.downloadAllType.selector()); + await delay(500); + } + ) + .addNextStep( + cartTourTargets.globusCollectionDropdown.selector(), + "For Globus downloads, you need to select a saved collection from this drop-down. If you haven't saved any collections, you can do so by clicking the 'Manage Collections' option.", 'top-start' ) .addNextStep( cartTourTargets.downloadAllBtn.selector(), - 'Then you would click this button to get the download script needed for all currently selected datasets in the cart.', + 'After selecting your collection, click this button to start the download for your selected cart items.', 'top-start', /* istanbul ignore next */ async () => { @@ -730,11 +758,6 @@ export const createCartItemsTour = ( await delay(300); } ) - .addNextStep( - cartTourTargets.removeItemsBtn.selector(), - 'We can remove all items from the cart with this button.', - 'right-start' - ) .addNextStep('body', 'This concludes the cart page tour.', 'center') .setOnFinish( /* istanbul ignore next */ @@ -758,6 +781,50 @@ export const createCartItemsTour = ( return tour; }; +export const createCollectionsFormTour = (): JoyrideTour => { + const tour = new JoyrideTour(TourTitles.ManageCollections) + .addNextStep( + manageCollectionsTourTargets.globusCollectionsForm.selector(), + "The 'Manage My Collections' form allows you to search for and save Globus collections which you can then select to perform Globus transfers." + ) + .addNextStep( + manageCollectionsTourTargets.searchCollectionInput.selector(), + "First, type your search text in here, then press 'Enter' or click the blue search button to the right." + ) + .addNextStep( + manageCollectionsTourTargets.globusSearchResults.selector(), + "The search results will be displayed in this table, where you can click 'Add' for the collections you wish to save.", + 'auto', + /* istanbul ignore next */ + async () => { + clickFirstElement( + manageCollectionsTourTargets.mySavedCollectionsPanel.selector() + ); + await delay(500); + } + ) + .addNextStep( + manageCollectionsTourTargets.mySavedCollections.selector(), + "Your currently saved collections are displayed in this table, where you can also 'Set' or 'Update' the file path to use for a specific collection. If the path is set for a specific collection, you won't have to set the path again when doing transfers to that collection.", + 'auto' + ) + .addNextStep('body', 'This concludes the cart page tour.', 'center') + .setOnFinish( + /* istanbul ignore next */ + () => { + // Clean-up step for when the tour is complete (or skipped) + return async () => { + clickFirstElement( + manageCollectionsTourTargets.mySavedCollectionsPanel.selector() + ); + await delay(300); + }; + } + ); + + return tour; +}; + export const createSearchCardTour = ( setCurrentPage: (page: number) => void ): JoyrideTour => { diff --git a/frontend/src/components/Globus/DatasetDownload.tsx b/frontend/src/components/Globus/DatasetDownload.tsx index ee36edee9..b6a557440 100644 --- a/frontend/src/components/Globus/DatasetDownload.tsx +++ b/frontend/src/components/Globus/DatasetDownload.tsx @@ -1,4 +1,4 @@ -import { DownloadOutlined } from '@ant-design/icons'; +import { DownloadOutlined, QuestionOutlined } from '@ant-design/icons'; import { Button, Card, @@ -25,7 +25,11 @@ import { REQUESTED_SCOPES, createGlobusAuthObject, } from '../../api'; -import { cartTourTargets } from '../../common/reactJoyrideSteps'; +import { + cartTourTargets, + createCollectionsFormTour, + manageCollectionsTourTargets, +} from '../../common/reactJoyrideSteps'; import { globusEnabledNodes, globusRedirectUrl } from '../../env'; import { RawSearchResults } from '../Search/types'; import CartStateKeys, { @@ -42,6 +46,10 @@ import { } from './types'; import { NotificationType, showError, showNotice } from '../../common/utils'; import { DataPersister } from '../../common/DataPersister'; +import { + RawTourState, + ReactJoyrideContext, +} from '../../contexts/ReactJoyrideContext'; type AlertModalState = { onCancelAction: () => void; @@ -67,6 +75,10 @@ const dp: DataPersister = DataPersister.Instance; const DatasetDownloadForm: React.FC> = () => { const [messageApi, contextHolder] = message.useMessage(); + // Tutorial state + const tourState: RawTourState = React.useContext(ReactJoyrideContext); + const { startSpecificTour } = tourState; + const [downloadIsLoading, setDownloadIsLoading] = useRecoilState( cartDownloadIsLoading ); @@ -904,6 +916,7 @@ const DatasetDownloadForm: React.FC> = () => { /> {selectedDownloadType === 'Globus' && (