diff --git a/README.md b/README.md index 8179910dc..d5535052d 100644 --- a/README.md +++ b/README.md @@ -90,3 +90,37 @@ The extraction tool is [`i18next-parser`](https://www.npmjs.com/package/i18next- ). To workaround this, specify static values in `i18n.ts` file under any top-level directory below `src/app`. For example, `src/app/Settings/i18n.ts`. + +## ADDING QUICKSTARTS + +To add a new quickstart, create a new tsx/ts file under `src/app/QuickStarts` with your Quick Start name, like `my-quickstart.tsx`. + +### Highlighting elements + +You can highlight an element on the page from within a quick start. The element that should be highlightable needs a data-quickstart-id attribute. Example: +``` + +``` + +In the quick start task description, you can add this type of markdown to target this element: +``` +Highlight [special button]{{highlight special-btn}} +``` + +### Copyable text + +You can have inline or block copyable text. + +#### Inline copyable text example +``` +`echo "Donec id est ante"`{{copy}} +``` + +#### Multiline copyable text example +``` + ``` + First line of text. + Second line of text. + ```{{copy}} +``` + diff --git a/locales/en/public.json b/locales/en/public.json index c7fe21c0d..685ef0e42 100644 --- a/locales/en/public.json +++ b/locales/en/public.json @@ -17,6 +17,15 @@ "CARD_DESCRIPTION_FULL": "This is a do-nothing placeholder with all the config.", "CARD_TITLE": "All Placeholder" }, + "AppLayout": { + "APP_LAUNCHER": { + "ABOUT": "About", + "DOCUMENTATION": "Documentation", + "GUIDED_TOUR": "Guided tour", + "HELP": "Help", + "QUICKSTARTS": "Quick Starts" + } + }, "AutomatedAnalysisCard": { "CARD_DESCRIPTION": "Assess common application performance and configuration issues", "CARD_DESCRIPTION_FULL": "Creates a recording and periodically evaluates various common problems in application configuration and performance. Results are displayed with scores from 0-100 with colour coding and in groups. This card should be unique on a dashboard.", diff --git a/package.json b/package.json index ccb04ddd1..eac4ec4b4 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-i18next": "^12.1.5", + "react-joyride": "^2.5.3", "react-redux": "^8.0.5", "react-router-last-location": "^2.0.1", "showdown": "^2.1.0" diff --git a/src/app/About/AboutCryostatModal.tsx b/src/app/About/AboutCryostatModal.tsx index dc0ed772d..84d95d925 100644 --- a/src/app/About/AboutCryostatModal.tsx +++ b/src/app/About/AboutCryostatModal.tsx @@ -38,6 +38,7 @@ import bkgImg from '@app/assets/about_background.png'; import cryostatLogo from '@app/assets/cryostat_icon_rgb_reverse.svg'; import build from '@app/build.json'; +import { portalRoot } from '@app/utils/utils'; import { AboutModal } from '@patternfly/react-core'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -46,18 +47,17 @@ import { AboutDescription } from './AboutDescription'; export const AboutCryostatModal = ({ isOpen, onClose }) => { const { t } = useTranslation(); return ( - <> - - - - + + + ); }; diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index 8932a0ddb..98ea390ec 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -38,6 +38,7 @@ import { AboutCryostatModal } from '@app/About/AboutCryostatModal'; import cryostatLogo from '@app/assets/cryostat_logo_hori_rgb_reverse.svg'; import build from '@app/build.json'; +import { useJoyride } from '@app/Joyride/JoyrideProvider'; import { NotificationCenter } from '@app/Notifications/NotificationCenter'; import { Notification, NotificationsContext } from '@app/Notifications/Notifications'; import { IAppRoute, navGroups, routes } from '@app/routes'; @@ -48,18 +49,21 @@ import { NotificationCategory } from '@app/Shared/Services/NotificationChannel.s import { ServiceContext } from '@app/Shared/Services/Services'; import { FeatureLevel } from '@app/Shared/Services/Settings.service'; import { useSubscriptions } from '@app/utils/useSubscriptions'; -import { openTabForUrl, portalRoot } from '@app/utils/utils'; +import { cleanDataId, openTabForUrl, portalRoot } from '@app/utils/utils'; import { Alert, AlertActionCloseButton, AlertGroup, AlertVariant, + ApplicationLauncher, + ApplicationLauncherItem, Brand, Button, Dropdown, DropdownGroup, DropdownItem, DropdownToggle, + Icon, Label, Masthead, MastheadBrand, @@ -92,8 +96,11 @@ import { } from '@patternfly/react-icons'; import * as _ from 'lodash'; import * as React from 'react'; +import { useTranslation } from 'react-i18next'; import { Link, matchPath, NavLink, useHistory, useLocation } from 'react-router-dom'; import { map } from 'rxjs/operators'; +import CryostatJoyride from '../Joyride/CryostatJoyride'; +import { GlobalQuickStartDrawer } from '../QuickStarts/QuickStartDrawer'; import { AuthModal } from './AuthModal'; import { SslErrorModal } from './SslErrorModal'; interface AppLayoutProps { @@ -105,10 +112,16 @@ const AppLayout: React.FC = ({ children }) => { const notificationsContext = React.useContext(NotificationsContext); const addSubscription = useSubscriptions(); const routerHistory = useHistory(); - - const [isNavOpen, setIsNavOpen] = React.useState(true); + const { t } = useTranslation(); + const { + setState: setJoyState, + state: joyState, + isNavBarOpen: joyNavOpen, + setIsNavBarOpen: setJoyNavOpen, + } = useJoyride(); + + const [isNavOpen, setIsNavOpen] = [joyNavOpen, setJoyNavOpen]; const [isMobileView, setIsMobileView] = React.useState(true); - const [isNavOpenMobile, setIsNavOpenMobile] = React.useState(false); const [showAuthModal, setShowAuthModal] = React.useState(false); const [showSslErrorModal, setShowSslErrorModal] = React.useState(false); const [aboutModalOpen, setAboutModalOpen] = React.useState(false); @@ -219,28 +232,33 @@ const AppLayout: React.FC = ({ children }) => { const dismissSslErrorModal = React.useCallback(() => setShowSslErrorModal(false), [setShowSslErrorModal]); - const onNavToggleMobile = React.useCallback(() => { - setIsNavOpenMobile((isNavOpenMobile) => !isNavOpenMobile); - }, [setIsNavOpenMobile]); - const onNavToggle = React.useCallback(() => { - setIsNavOpen((isNavOpen) => !isNavOpen); - }, [setIsNavOpen]); + setIsNavOpen((isNavOpen) => { + if (joyState.run === true && joyState.stepIndex === 1 && !isNavOpen) { + setJoyState({ stepIndex: 2 }); + } + return !isNavOpen; + }); + }, [setIsNavOpen, joyState, setJoyState]); + // prevent page resize to close nav during tour const onPageResize = React.useCallback( (props: { mobileView: boolean; windowSize: number }) => { - setIsMobileView(props.mobileView); + if (joyState.run === false) { + setIsMobileView(props.mobileView); + setIsNavOpen(!props.mobileView); + } }, - [setIsMobileView] + [joyState, setIsMobileView, setIsNavOpen] ); const mobileOnSelect = React.useCallback( (_) => { if (isMobileView) { - setIsNavOpenMobile(false); + setIsNavOpen(false); } }, - [isMobileView, setIsNavOpenMobile] + [isMobileView, setIsNavOpen] ); const handleSettingsButtonClick = React.useCallback(() => { @@ -326,38 +344,36 @@ const AppLayout: React.FC = ({ children }) => { openTabForUrl(build.discussionUrl); }, []); - const helpItems = React.useMemo( - () => [ - - Documentation - - , - - Help - - , - - About - , - - - - Quick Starts - - - , - ], - [handleOpenDocumentation, handleOpenDiscussion, handleOpenAboutModal] - ); - - const HelpToggle = React.useMemo( - () => ( - - - - ), - [handleHelpToggle] - ); + const handleOpenGuidedTour = React.useCallback(() => { + setJoyState({ run: true }); + }, [setJoyState]); + + const helpItems = React.useMemo(() => { + return [ + {t('AppLayout.APP_LAUNCHER.QUICKSTARTS')}} + />, + + {t('AppLayout.APP_LAUNCHER.DOCUMENTATION')} + + + + , + + {t('AppLayout.APP_LAUNCHER.GUIDED_TOUR')} + , + + {t('AppLayout.APP_LAUNCHER.HELP')} + + + + , + + {t('AppLayout.APP_LAUNCHER.ABOUT')} + , + ]; + }, [t, handleOpenDocumentation, handleOpenGuidedTour, handleOpenDiscussion, handleOpenAboutModal]); const levelBadge = React.useCallback((level: FeatureLevel) => { return ( @@ -403,17 +419,21 @@ const AppLayout: React.FC = ({ children }) => { diff --git a/src/app/Dashboard/AddCard.tsx b/src/app/Dashboard/AddCard.tsx index 38298d351..ec39036c6 100644 --- a/src/app/Dashboard/AddCard.tsx +++ b/src/app/Dashboard/AddCard.tsx @@ -202,7 +202,7 @@ export const AddCard: React.FC = (_) => { }} >
- + @@ -252,7 +252,7 @@ export const AddCard: React.FC = (_) => { Cards added to this Dashboard layout present information at a glance about the selected target. The layout is preserved for all targets viewed on this client. - @@ -357,7 +357,7 @@ const PropsConfigForm = ({ onChange, ...props }: PropsConfigFormProps) => { break; } return ( - + {input} ); diff --git a/src/app/Dashboard/Dashboard.tsx b/src/app/Dashboard/Dashboard.tsx index 3d750ff02..953f082c3 100644 --- a/src/app/Dashboard/Dashboard.tsx +++ b/src/app/Dashboard/Dashboard.tsx @@ -67,7 +67,6 @@ import { DashboardCard } from './DashboardCard'; import { DashboardCardActionMenu } from './DashboardCardActionMenu'; import { DashboardLayoutConfig } from './DashboardLayoutConfig'; import { JvmDetailsCardDescriptor } from './JvmDetails/JvmDetailsCard'; -import { QuickStartsCardDescriptor } from './Quickstart/QuickStartsCard'; export interface Sized { minimum: T; @@ -253,7 +252,6 @@ export const getDashboardCards: (featureLevel?: FeatureLevel) => DashboardCardDe MBeanMetricsChartCardDescriptor, NonePlaceholderCardDescriptor, AllPlaceholderCardDescriptor, - QuickStartsCardDescriptor, ]; return cards.filter((card) => card.featureLevel >= featureLevel); }; diff --git a/src/app/Dashboard/DashboardCard.tsx b/src/app/Dashboard/DashboardCard.tsx index f85a3d4a6..5b4a1e186 100644 --- a/src/app/Dashboard/DashboardCard.tsx +++ b/src/app/Dashboard/DashboardCard.tsx @@ -94,6 +94,7 @@ export const DashboardCard: React.FC = ({ onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} draggable // draggable is required for drag events to fire + data-quickstart-id="card-draggable-grip" > {cardHeader} diff --git a/src/app/Dashboard/DashboardLayoutConfig.tsx b/src/app/Dashboard/DashboardLayoutConfig.tsx index d00e23cb9..42a1f5ab4 100644 --- a/src/app/Dashboard/DashboardLayoutConfig.tsx +++ b/src/app/Dashboard/DashboardLayoutConfig.tsx @@ -214,6 +214,7 @@ export const DashboardLayoutConfig: React.FunctionComponent} + data-quickstart-id="dashboard-new-btn" > {t('NEW', { ns: 'common' })} @@ -230,6 +231,7 @@ export const DashboardLayoutConfig: React.FunctionComponent handleRenameLayout(currLayout.name)} icon={} + data-quickstart-id="dashboard-rename-btn" /> ), [t, handleRenameLayout, currLayout.name] @@ -243,6 +245,7 @@ export const DashboardLayoutConfig: React.FunctionComponent} + data-quickstart-id="dashboard-upload-btn" > {t('UPLOAD', { ns: 'common' })} @@ -258,6 +261,7 @@ export const DashboardLayoutConfig: React.FunctionComponent} + data-quickstart-id="dashboard-download-btn" > {t('DOWNLOAD', { ns: 'common' })} @@ -275,6 +279,7 @@ export const DashboardLayoutConfig: React.FunctionComponent handleDeleteButton(ev, currLayout.name)} icon={} + data-quickstart-id="dashboard-delete-btn" > {t('DELETE', { ns: 'common' })} @@ -340,7 +345,12 @@ export const DashboardLayoutConfig: React.FunctionComponent ( - + {currLayout.name} )} diff --git a/src/app/Dashboard/Quickstart/QuickStartsCard.tsx b/src/app/Dashboard/Quickstart/QuickStartsCard.tsx deleted file mode 100644 index 98016152f..000000000 --- a/src/app/Dashboard/Quickstart/QuickStartsCard.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright The Cryostat Authors - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or data - * (collectively the "Software"), free of charge and under any and all copyright - * rights in the Software, and any and all patent rights owned or freely - * licensable by each licensor hereunder covering either (i) the unmodified - * Software as contributed to or provided by such licensor, or (ii) the Larger - * Works (as defined below), to deal in both - * - * (a) the Software, and - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software (each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * The above copyright notice and either this complete permission notice or at - * a minimum a reference to the UPL must be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -import { FeatureLevel } from '@app/Shared/Services/Settings.service'; -import { - QuickStartCatalogPage, - QuickStartContainer, - QuickStartContainerProps, - useLocalStorage, -} from '@patternfly/quickstarts'; -import { CardActions, CardBody, CardHeader, CardTitle } from '@patternfly/react-core'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { DashboardCardDescriptor, DashboardCardProps, DashboardCardSizes } from '../Dashboard'; -import { DashboardCard } from '../DashboardCard'; -import { allQuickStarts } from './dashboard-quickstarts'; - -export interface QuickStartsCardProps extends DashboardCardProps {} - -const QuickStartsCard: React.FunctionComponent = (props) => { - const { t, i18n } = useTranslation(); - const [activeQuickStartID, setActiveQuickStartID] = useLocalStorage('quickstartId', ''); - const [allQuickStartStates, setAllQuickStartStates] = useLocalStorage('quickstarts', {}); - - const drawerProps: QuickStartContainerProps = { - quickStarts: allQuickStarts, - activeQuickStartID, - allQuickStartStates, - setActiveQuickStartID, - setAllQuickStartStates, - language: i18n.language, - alwaysShowTaskReview: true, - }; - return ( - - {t('QuickStartsCard.TITLE')} - {...props.actions || []} - - } - > - - - - - - - ); -}; - -const QuickStartsCardSizes = { - span: { - minimum: 6, - default: 12, - maximum: 12, - }, - height: { - minimum: Number.NaN, - default: Number.NaN, - maximum: Number.NaN, - }, -} as DashboardCardSizes; - -export const QuickStartsCardDescriptor: DashboardCardDescriptor = { - featureLevel: FeatureLevel.BETA, - title: 'QuickStartsCard.CARD_TITLE', - cardSizes: QuickStartsCardSizes, - description: 'QuickStartsCard.CARD_DESCRIPTION', - descriptionFull: 'QuickStartsCard.CARD_DESCRIPTION_FULL', - component: QuickStartsCard, - propControls: [], -} as DashboardCardDescriptor; diff --git a/src/app/Dashboard/i18n.ts b/src/app/Dashboard/i18n.ts index 0547e268c..f1092f673 100644 --- a/src/app/Dashboard/i18n.ts +++ b/src/app/Dashboard/i18n.ts @@ -58,9 +58,6 @@ * t('CHART_CARD.PROP_CONTROLS.DATA_WINDOW.DESCRIPTION') * t('CHART_CARD.PROP_CONTROLS.REFRESH_PERIOD.NAME') * t('CHART_CARD.PROP_CONTROLS.REFRESH_PERIOD.DESCRIPTION') - * t('QuickStartsCard.CARD_TITLE') - * t('QuickStartsCard.CARD_DESCRIPTION') - * t('QuickStartsCard.CARD_DESCRIPTION_FULL') * t('NonePlaceholderCard.CARD_TITLE') * t('NonePlaceholderCard.CARD_DESCRIPTION') * t('NonePlaceholderCard.CARD_DESCRIPTION_FULL') diff --git a/src/app/Events/EventTypes.tsx b/src/app/Events/EventTypes.tsx index 8a4914762..e9772f829 100644 --- a/src/app/Events/EventTypes.tsx +++ b/src/app/Events/EventTypes.tsx @@ -41,7 +41,7 @@ import { ServiceContext } from '@app/Shared/Services/Services'; import { NO_TARGET } from '@app/Shared/Services/Target.service'; import { useSort } from '@app/utils/useSort'; import { useSubscriptions } from '@app/utils/useSubscriptions'; -import { hashCode, sortResouces } from '@app/utils/utils'; +import { hashCode, sortResources } from '@app/utils/utils'; import { Toolbar, ToolbarContent, @@ -190,7 +190,7 @@ export const EventTypes: React.FC = (_) => { includesSubstr(t.typeId, filterText) || includesSubstr(t.description, filterText) || includesSubstr(getCategoryString(t), filterText); - return sortResouces(sortBy, types.filter(withFilters), mapper, getTransform); + return sortResources(sortBy, types.filter(withFilters), mapper, getTransform); }, [types, filterText, sortBy]); const displayedTypeRowData = React.useMemo(() => { diff --git a/src/app/Joyride/CryostatJoyride.tsx b/src/app/Joyride/CryostatJoyride.tsx new file mode 100644 index 000000000..757c9d1fb --- /dev/null +++ b/src/app/Joyride/CryostatJoyride.tsx @@ -0,0 +1,277 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import cryostatLogo from '@app/assets/cryostat_logo_vert_rgb_default.svg'; +import build from '@app/build.json'; +import { useJoyride } from '@app/Joyride/JoyrideProvider'; +import JoyrideTooltip from '@app/Joyride/JoyrideTooltip'; +import React from 'react'; +import ReactJoyride, { CallBackProps, ACTIONS, EVENTS, STATUS } from 'react-joyride'; +interface CryostatJoyrideProps { + children: React.ReactNode; +} + +const CryostatJoyride: React.FC = (props) => { + const { + setState, + state: { run, stepIndex, steps }, + isNavBarOpen, + } = useJoyride(); + + React.useEffect(() => { + setState({ + steps: [ + { + content: ( +
+

+ Cryostat is a cloud-based profiling application for managing JFR recordings in + containerized Java environments. +

+
+

+ There are many other features that Cryostat provides, such as the ability to download + recordings, generate reports, and more. +

+
+ ), + placement: 'center', + title: ( +
+ Cryostat Logo +

+ Welcome to Cryostat! +

+
+ ), + target: 'body', + disableBeacon: true, + }, + { + content:

Open the navigation bar!

, + target: '*[data-tour-id="nav-toggle-btn"]', + disableBeacon: true, + placement: 'bottom', + spotlightClicks: true, + placementBeacon: 'right', + }, + { + title: 'Dashboard View', + content: ( +
+

+ The Dashboard provides a high-level overview of Cryostat and the target JVM with the + use of Dashboard Cards. +

+
+

There are various dashboard cards that can be configured to display different metrics and charts.

+
+ ), + target: '*[data-tour-id="dashboard"]', + placement: 'right', + }, + { + title: 'Topology View', + content: ( +
+

+ The Topology view provides a visual representation of Cryostat and the deployment + model. Start, stop, and delete recordings on multiple targets at a time from this view. +

+
+ ), + target: '*[data-tour-id="topology"]', + placement: 'right', + }, + { + title: 'Automated Rules', + content: ( +

+ Create, delete, enable, and view Cryostat Automated Rules in this view. Automated Rules + allow you start recordings on target JVMs based on a set of conditions. +

+ ), + target: '*[data-tour-id="automatedrules"]', + placement: 'right', + }, + { + title: 'JFR Recordings', + content: ( +

+ The Recordings view provides a list of all active recordings that are currently being + recorded on the target JVM. Start, stop, download, delete recordings from this view. +

+ ), + target: '*[data-tour-id="recordings"]', + placement: 'right', + }, + { + title: 'Archives View', + content: ( +

+ The Archives view provides a list of all saved recordings that have been saved to + Cryostat. Download, delete, and generate reports from this view. +

+ ), + target: '*[data-tour-id="archives"]', + placement: 'right', + }, + { + title: 'Events', + content: ( +

+ The Events page lists the Event Templates that can be used for creating + Flight Recordings. It also details the JFR Event Types that can be recorded within each + target JVM. +

+ ), + target: '*[data-tour-id="events"]', + placement: 'right', + }, + { + title: 'Security', + content: ( +

+ The Security tab allows you to add Credentials and{' '} + SSL Certificates for Cryostat to use when connecting to remote targets. +

+ ), + target: '*[data-tour-id="security"]', + placement: 'right', + }, + { + title: 'Settings', + content: ( +

+ Set your Cryostat preferences, such as the theme, locale, notification settings, and + more. +

+ ), + target: '*[data-tour-id="settings-link"]', + }, + { + title: 'Help', + content: ( +

+ Restart this tour or access our new quick starts where you can learn more about using + Cryostat in your environment. +

+ ), + target: '*[data-tour-id="application-launcher"]', + }, + { + title: 'You’re ready to go!', + content: ( +

+ Stay up-to-date with everything Cryostat on our{' '} + + blog + {' '} + or continue to learn more in our{' '} + + documentation guides + + . +

+ ), + placement: 'center', + target: 'body', + disableBeacon: true, + }, + ], + }); + }, [setState]); + + // index 0 -> Get Started + // index 1 -> Navigation + // index 2 -> Dashboard + // etc... + const callback = React.useCallback( + (data: CallBackProps) => { + const { action, index, status, type } = data; + if (([STATUS.FINISHED, STATUS.SKIPPED] as string[]).includes(status)) { + setState({ run: false, stepIndex: 0 }); + } else if (action === 'close' && type === 'step:before') { + setState({ run: false, stepIndex: 0 }); + } else if (([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND] as string[]).includes(type)) { + if (action === ACTIONS.PREV) { + switch (index) { + case 0: + case 2: + setState({ stepIndex: 0 }); + break; + default: + setState({ stepIndex: index - 1 }); + } + } else { + switch (index) { + case 0: + if (isNavBarOpen) { + setState({ stepIndex: 2 }); + } else { + setState({ stepIndex: 1 }); + } + break; + default: + setState({ stepIndex: index + 1 }); + } + } + } + }, + [setState, isNavBarOpen] + ); + + return ( + <> + + {props.children} + + ); +}; + +export default CryostatJoyride; diff --git a/src/app/QuickStarts/QuickStarts.tsx b/src/app/Joyride/JoyrideProvider.tsx similarity index 58% rename from src/app/QuickStarts/QuickStarts.tsx rename to src/app/Joyride/JoyrideProvider.tsx index 5265498fe..d1ff0b38b 100644 --- a/src/app/QuickStarts/QuickStarts.tsx +++ b/src/app/Joyride/JoyrideProvider.tsx @@ -35,46 +35,52 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import { LoadingView } from '@app/LoadingView/LoadingView'; -import { - QuickStartCatalogPage, - QuickStartContainer, - QuickStartContainerProps, - useLocalStorage, -} from '@patternfly/quickstarts'; +import useSetState from '@app/utils/useSetState'; import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { withRouter } from 'react-router-dom'; -import { allQuickStarts } from './all-quickstarts'; +import { Step } from 'react-joyride'; -export interface QuickStartsProps {} +export interface JoyrideState { + run: boolean; + stepIndex: number; + steps: Step[]; +} -const QuickStarts: React.FunctionComponent = (_) => { - const { t, i18n } = useTranslation(); - const [activeQuickStartID, setActiveQuickStartID] = useLocalStorage('quickstartId', ''); - const [allQuickStartStates, setAllQuickStartStates] = useLocalStorage('quickstarts', {}); +const defaultState = { + run: false, + stepIndex: 0, + steps: [] as Step[], +}; + +export interface JoyrideContextType { + state: JoyrideState; + setState: (patch: Partial | ((previousState: JoyrideState) => Partial)) => void; + isNavBarOpen: boolean; + setIsNavBarOpen: (isOpen: React.SetStateAction) => void; +} + +/* eslint-disable @typescript-eslint/no-empty-function */ +export const JoyrideContext = React.createContext({ + state: defaultState, + setState: () => undefined, + isNavBarOpen: true, + setIsNavBarOpen: () => undefined, +}); +/* eslint-enable @typescript-eslint/no-empty-function */ - const drawerProps: QuickStartContainerProps = { - quickStarts: allQuickStarts, - activeQuickStartID, - allQuickStartStates, - setActiveQuickStartID, - setAllQuickStartStates, - language: i18n.language, - alwaysShowTaskReview: true, - }; +export const JoyrideProvider: React.FC<{ children }> = (props) => { + const [state, setState] = useSetState(defaultState); + const [isNavBarOpen, setIsNavBarOpen] = React.useState(true); + const value = React.useMemo( + () => ({ state, setState, isNavBarOpen, setIsNavBarOpen }), + [state, setState, isNavBarOpen, setIsNavBarOpen] + ); return ( - }> - - - - + + {props.children} + ); }; -export default withRouter(QuickStarts); +export const useJoyride = (): JoyrideContextType => { + return React.useContext(JoyrideContext); +}; diff --git a/src/app/Joyride/JoyrideTooltip.tsx b/src/app/Joyride/JoyrideTooltip.tsx new file mode 100644 index 000000000..f5756c5ec --- /dev/null +++ b/src/app/Joyride/JoyrideTooltip.tsx @@ -0,0 +1,128 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import { + ActionList, + ActionListGroup, + ActionListItem, + Button, + Card, + CardBody, + CardFooter, + CardTitle, + Split, + SplitItem, + Text, + TextContent, +} from '@patternfly/react-core'; +import React from 'react'; +import { TooltipRenderProps } from 'react-joyride'; + +const JoyrideTooltip: React.FC = ({ + backProps, + primaryProps, + skipProps, + tooltipProps, + index, + isLastStep, + step, + size, +}) => { + const { title, content } = step; + + const isFirstStep = React.useMemo(() => { + return index == 0; + }, [index]); + + const footer = React.useMemo(() => { + return ( + + {!isFirstStep && !isLastStep && ( + + + + { + `Step ${index - 1}/${size - 3}` // Index starts at 0, tour starts at 2, there are 3 steps that don't need a footer + } + + + + )} + + + + + {isFirstStep ? ( + + ) : ( + + )} + + + { + + } + + + + + + ); + }, [isFirstStep, isLastStep, backProps, primaryProps, skipProps, index, size]); + + return ( +
+ + {title} + {content} + {index !== 1 && {footer}} + +
+ ); +}; + +export default JoyrideTooltip; diff --git a/src/app/QuickStarts/QuickStartDrawer.tsx b/src/app/QuickStarts/QuickStartDrawer.tsx new file mode 100644 index 000000000..c7bfba9e5 --- /dev/null +++ b/src/app/QuickStarts/QuickStartDrawer.tsx @@ -0,0 +1,127 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import build from '@app/build.json'; +import { LoadingView } from '@app/LoadingView/LoadingView'; +import { allQuickStarts } from '@app/QuickStarts/all-quickstarts'; +import { SessionState } from '@app/Shared/Services/Login.service'; +import { ServiceContext } from '@app/Shared/Services/Services'; +import { useFeatureLevel } from '@app/utils/useFeatureLevel'; +import { useSubscriptions } from '@app/utils/useSubscriptions'; +import { + QuickStartContext, + QuickStartDrawer, + useLocalStorage, + useValuesForQuickStartContext, +} from '@patternfly/quickstarts'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; + +const LINK_LABEL = "[\\d\\w\\s-()$!&']+"; // has extra &' in matcher +const HIGHLIGHT_ACTIONS = ['highlight']; // use native quickstarts highlight markdown extension +const SELECTOR_ID = `[\\w-&]+`; // has extra &' + +// [linkLabel]{{action id}} +const HIGHLIGHT_REGEXP = new RegExp(`\\[(${LINK_LABEL})\\]{{(${HIGHLIGHT_ACTIONS.join('|')}) (${SELECTOR_ID})}}`, 'g'); +export interface GlobalQuickStartDrawerProps { + children: React.ReactNode; +} + +export const GlobalQuickStartDrawer: React.FC = ({ children }) => { + const { i18n } = useTranslation(); + const context = React.useContext(ServiceContext); + const addSubscription = useSubscriptions(); + const [activeQuickStartID, setActiveQuickStartID] = useLocalStorage('quickstartId', ''); + const [allQuickStartStates, setAllQuickStartStates] = useLocalStorage('quickstarts', {}); + + const activeLevel = useFeatureLevel(); + + React.useEffect(() => { + addSubscription( + context.login.getSessionState().subscribe((s) => { + if (s == SessionState.NO_USER_SESSION) { + setActiveQuickStartID(''); + } + }) + ); + }, [addSubscription, context.login, setActiveQuickStartID]); + + const filteredQuickStarts = React.useMemo(() => { + return allQuickStarts.filter((qs) => qs.metadata.featureLevel >= activeLevel); + }, [activeLevel]); + + // useValues... hook seems to use first render value of allQuickStarts, so we need to re-render on activeLevel change + const valuesForQuickStartContext = { + ...useValuesForQuickStartContext({ + activeQuickStartID, + setActiveQuickStartID, + allQuickStartStates, + setAllQuickStartStates, + language: i18n.language, + markdown: { + extensions: [ + { + // this only takes into effect if the regex matches the HIGHLIGHT_REGEXP i.e. contains the extra matching tokens the patternfly/quickstarts highlight extension regex does not + type: 'lang', + regex: HIGHLIGHT_REGEXP, + replace: (text: string, linkLabel: string, linkType: string, linkId: string): string => { + if (!linkLabel || !linkType || !linkId) return text; + return ``; + }, + }, + { + // replace [APP] with bolded productName like Cryostat + type: 'output', + regex: new RegExp(`\\[APP\\]`, 'g'), + replace: (_text: string): string => { + return `${build.productName}`; + }, + }, + ], + }, + }), + allQuickStarts: filteredQuickStarts, + }; + + return ( + }> + + {children} + + + ); +}; diff --git a/src/app/QuickStarts/quickstarts/my-quickstart.ts b/src/app/QuickStarts/QuickStartsCatalogPage.tsx similarity index 67% rename from src/app/QuickStarts/quickstarts/my-quickstart.ts rename to src/app/QuickStarts/QuickStartsCatalogPage.tsx index f5380d2ad..cdbb50ad0 100644 --- a/src/app/QuickStarts/quickstarts/my-quickstart.ts +++ b/src/app/QuickStarts/QuickStartsCatalogPage.tsx @@ -35,35 +35,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import cryostatLogo from '@app/assets/cryostat_icon_rgb_default.svg'; -import { QuickStart } from '@patternfly/quickstarts'; +import { QuickStartCatalogPage } from '@patternfly/quickstarts'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { withRouter } from 'react-router-dom'; -export const SampleQuickStart: QuickStart = { - apiVersion: 'v2.3.0', - metadata: { - name: 'sample-quickstart', - }, - spec: { - displayName: 'Sample QuickStart', - durationMinutes: 1, - icon: cryostatLogo, - description: 'This is a sample quickstart.', - introduction: '### This is a sample quickstart.', - tasks: [ - { - title: 'Task 1', - description: 'This is a sample task.', - review: { - instructions: '#### Verify that you have done the task.', - failedTaskHelp: 'This is how you can fix the failed task.', - }, - }, - ], - conclusion: '
You have completed the sample quickstart.
', - nextQuickStart: ['add-card-quickstart'], - type: { - text: 'Introduction', - color: 'green', - }, - }, +export interface QuickStartsCatalogPageProps {} + +const QuickStartsCatalogPage: React.FunctionComponent = (_) => { + const { t } = useTranslation(); + + // TODO: Quick start categories (patternfly/quickstarts supports this through individual components) + // e.g. Dashboard Quick Starts, Topology Quick Starts, Recording Quick Starts, etc. + return ( + + ); }; + +export default withRouter(QuickStartsCatalogPage); diff --git a/src/app/QuickStarts/all-quickstarts.ts b/src/app/QuickStarts/all-quickstarts.ts index b666c22f1..9a5c7345f 100644 --- a/src/app/QuickStarts/all-quickstarts.ts +++ b/src/app/QuickStarts/all-quickstarts.ts @@ -36,9 +36,17 @@ * SOFTWARE. */ import { QuickStart } from '@patternfly/quickstarts'; -import { AddCardQuickStart } from './quickstarts/add-card-quickstart'; -// import { GenericQuickStart } from './quickstarts/generic-quickstart'; -import { SampleQuickStart } from './quickstarts/my-quickstart'; +import AutomatedRulesQuickStart from './quickstarts/automated-rules-quickstart'; +import DashboardQuickStart from './quickstarts/dashboard-quickstart'; +import GenericQuickStart from './quickstarts/generic-quickstart'; +import SettingsQuickStart from './quickstarts/settings-quickstart'; +import RecordingQuickStart from './quickstarts/start-a-recording'; -// Add your quick start here e.g. [GenericQuickStart, SampleQuickStart, AddCardQuickStart] -export const allQuickStarts: QuickStart[] = [SampleQuickStart, AddCardQuickStart]; +// Add your quick start here e.g. [GenericQuickStart, ...] +export const allQuickStarts: QuickStart[] = [ + AutomatedRulesQuickStart, + DashboardQuickStart, + GenericQuickStart, + RecordingQuickStart, + SettingsQuickStart, +]; diff --git a/src/app/QuickStarts/quickstarts/add-card-quickstart.ts b/src/app/QuickStarts/quickstarts/add-card-quickstart.ts deleted file mode 100644 index 5e774791f..000000000 --- a/src/app/QuickStarts/quickstarts/add-card-quickstart.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright The Cryostat Authors - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or data - * (collectively the "Software"), free of charge and under any and all copyright - * rights in the Software, and any and all patent rights owned or freely - * licensable by each licensor hereunder covering either (i) the unmodified - * Software as contributed to or provided by such licensor, or (ii) the Larger - * Works (as defined below), to deal in both - * - * (a) the Software, and - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software (each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * The above copyright notice and either this complete permission notice or at - * a minimum a reference to the UPL must be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -import cryostatLogo from '@app/assets/cryostat_icon_rgb_default.svg'; -import build from '@app/build.json'; -import { QuickStart } from '@patternfly/quickstarts'; - -export const AddCardQuickStart: QuickStart = { - apiVersion: 'v2.3.0', - metadata: { - name: 'add-card-quickstart', - }, - spec: { - displayName: 'Adding/Removing Cards', - durationMinutes: 1, - icon: cryostatLogo, - description: `Add and remove cards from the ${build.productName} Dashboard.`, - introduction: '### This is a sample quickstart.', - tasks: [ - { - title: 'Add a Card', - description: `### We will add a card to the ${build.productName} Dashboard. -1. Go to the bottom of this page, and press the **Add** button on the 'Add Card' card. -2. Select a card type. -3. Press **Finish** to add the card to the dashboard.`, - review: { - instructions: `#### To verify the card was added: -1. Notice the new card located on the Dashboard. - `, - failedTaskHelp: 'Try the steps again.', - }, - summary: { - success: 'You have successfully added a card.', - failed: 'Try the steps again.', - }, - }, - { - title: 'Remove a Card', - description: `### We will remove a card to the ${build.productName} Dashboard. -1. Find the card was created in Step 1. -2. Press the kebab on the top right of that card's header. -3. Press **Remove Card**.`, - summary: { - success: 'You have successfully removed a card.', - failed: 'Try the steps again.', - }, - }, - ], - prerequisites: ['Complete the **Sample Quickstart** quick start.'], - conclusion: 'You have completed this quickstart on adding and removing cards.', - }, -}; diff --git a/src/app/QuickStarts/quickstarts/automated-rules-quickstart.tsx b/src/app/QuickStarts/quickstarts/automated-rules-quickstart.tsx new file mode 100644 index 000000000..be291d051 --- /dev/null +++ b/src/app/QuickStarts/quickstarts/automated-rules-quickstart.tsx @@ -0,0 +1,164 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import cryostatLogoIcon from '@app/assets/cryostat_icon_rgb_default.svg'; +import cryostatLogo from '@app/assets/cryostat_logo_vert_rgb_default.svg'; +import build from '@app/build.json'; +import { FeatureLevel } from '@app/Shared/Services/Settings.service'; +import { QuickStart } from '@patternfly/quickstarts'; + +// TODO: Add quickstarts based on the following example: +const AutomatedRulesQuickStart: QuickStart = { + apiVersion: 'v2.3.0', + metadata: { + name: 'automated-rules-quickstart', + featureLevel: FeatureLevel.PRODUCTION, + }, + spec: { + version: 2.3, + displayName: 'Automated Rules', + durationMinutes: 10, + icon: cryostatLogoIcon, + description: `Learn about automated rules in [APP] and how to create one.`, + prerequisites: ['Start a Recording'], + introduction: ` +# Automated Rules +Automated Rules are configurations that instruct [APP] to create JDK Flight Recordings on matching target JVM applications. Each Automated Rule specifies parameters for which Event Template to use, how much data should be kept in the application recording buffer, and how frequently Cryostat should copy the application recording buffer into Cryostat’s own archived storage. + +Once you’ve created a rule, [APP] immediately matches it against all existing discovered targets and starts your flight recording. [APP] will also apply the rule to newly discovered targets that match its definition. You can create multiple rules to match different subsets of targets or to layer different recording options for your needs. +In this quick start, you will use [APP] to create an automated rule that will start a recording on an existing target JVM. + +### What you'll learn + +- How to create an automated rule in [APP] +- How to use match expressions to match one or more target JVMs + +### What you'll need + +- A running instance of [APP] which has discovered at least one target JVM +- JMX auth credentials for the target JVM (if required) + + `, + tasks: [ + { + title: 'Go to the Automated Rules page', + description: `1. Click the [Automated Rules]{{highlight nav-automatedrules-tab}} tab in the [APP] console navigation bar.`, + review: { + instructions: '#### Verify that you see the Automated Rules page.', + failedTaskHelp: + 'If you do not see the navigation bar, you can click the `☰` button in the [top left corner of the page]{{highlight nav-toggle-btn}}.', + }, + }, + { + title: 'Create a new Automated Rule', + description: ` +1. Click the [Create]{{highlight create-rule-btn}} button. +[Read the [About Automated Rules]{{highlight about-rules}} section of the for more information.]{{admonition tip}} +`, + review: { + instructions: '#### Verify that you see the Automated Rules creation form.', + failedTaskHelp: 'If you do not see the creation form, follow the previous steps again.', + }, + }, + { + title: 'Fill out the Automated Rule form', + description: ` +The Automated Rule creation form has several fields that you can fill out to create a new rule. Each field has helper text that explains what the field is for. + +**The most important field is the [Match Expression]{{highlight rule-matchexpr}} field.** This field is used to match one or more target JVMs. The match expression is a Java-like code snippet that is matched against each target JVM. For example, if you wanted to match all discovered target JVMs, try using the match expression: \`true\`{{copy}}. + +[Use the [Match Expression Visualizer]{{highlight match-expr-card}} to test your match expression against the target JVMs that are currently discovered by [APP]. Matched targets will be unfaded in the Graph view, and listed in the List View.]{{admonition tip}} + +1. Select a target JVM from the [Target]{{highlight rule-target}} dropdown menu. +2. Enter a [Match Expression]{{highlight rule-matchexpr}} for the rule. Try using the match hint to help you create a match expression. +3. Note the target JVM details code block in the tester. These details can be used in the match expression. + +**To create a new rule, you must fill out the following required fields:** +1. Enter a name for the rule in the [Name]{{highlight rule-name}} field. +2. Enter a [Match Expression]{{highlight rule-matchexpr}} for the rule. +3. Select the [Event Template]{{highlight rule-evt-template}} you want to use for the rule. + +**The rest of the fields are optional and not required for this quick start: \`[Description, Maximum Size, Maximum Age, Maximum Age, Archival Period, Initial Delay, and Preserved Archives]\`.** + +[Learn more about these other Automated Rule attributes in the [Cryostat documentation](https://cryostat.io/guides/#create-an-automated-rule).]{{admonition tip}} + +When you are finished, click the [Create]{{highlight rule-create-btn}} button. + +`, + review: { + instructions: '#### Verify that you see the new rule in the list of rules.', + failedTaskHelp: `If you do not see the new rule, follow the previous steps again. + If you cannot create the rule, check that you have entered valid values for each required field.`, + }, + }, + { + title: 'Find the recording that was created by the rule', + description: ` +The rule we just created should have created a new recording on the target JVM that we selected. Let's find the recording. +1. Click the [Recordings]{{highlight nav-recordings-tab}} tab in the [APP] console navigation bar. +2. Click the [Target]{{highlight recordings-target}} dropdown menu and select the target JVM that you used to create the rule, if not already selected. + +There should now be a new recording in the list of recordings started on the selected target JVM. + +This recording was created by the rule that we just created and should have a name that matches the name of the rule like \`auto_\`. + +[If you set any other attributes on the rule, you should see those attributes reflected in the recording.]{{admonition note}} +`, + review: { + instructions: + '#### Verify that you see the new recording with the correct Automated Rule recording naming scheme in the list of recordings.', + failedTaskHelp: + 'If you do not see the new recording, try verifying that your rule match expression correctly matches the target JVM. Also make sure that the rule is enabled, and that the target JVM is still running, and selected in the Target dropdown menu.', + }, + }, + ], + conclusion: ` +
+

You completed the Automated Rules quick start!

+
+ Cryostat Logo +
+

For more information about the Automated Rules feature, read our guides on the Cryostat documentation.

+
`, + type: { + text: 'Featured', + color: 'blue', + }, + }, +}; + +export default AutomatedRulesQuickStart; diff --git a/src/app/QuickStarts/quickstarts/generic-quickstart.ts b/src/app/QuickStarts/quickstarts/cryostat-link-quickstart.tsx similarity index 78% rename from src/app/QuickStarts/quickstarts/generic-quickstart.ts rename to src/app/QuickStarts/quickstarts/cryostat-link-quickstart.tsx index c8bb49fa6..826f6b451 100644 --- a/src/app/QuickStarts/quickstarts/generic-quickstart.ts +++ b/src/app/QuickStarts/quickstarts/cryostat-link-quickstart.tsx @@ -39,32 +39,30 @@ import cryostatLogo from '@app/assets/cryostat_icon_rgb_default.svg'; import build from '@app/build.json'; import { QuickStart } from '@patternfly/quickstarts'; -// TODO: Add quickstarts based on the following example: -export const GenericQuickStart: QuickStart = { +// TODO: Put link quickstarts in a separate QuickStartCatalogSection +const CryostatLinkQuickStart: QuickStart = { apiVersion: 'v2.3.0', metadata: { - name: 'generic-quickstart', + name: 'cryostat-link-quickstart', + instructional: true, }, spec: { - displayName: 'Getting Started with', + version: 2.3, + displayName: 'Cryostat Upstream Documentation', durationMinutes: 1, icon: cryostatLogo, - description: `Get started with ${build.productName}.`, + description: `Link to Cryostat's upstream documentation.`, + prerequisites: [''], introduction: '### This is a generic quickstart.', - tasks: [ - { - title: 'Get started', - description: `### We will press the notifications bell icon on the top right. -1. Press the bell icon.`, - }, - ], - conclusion: `You finished **Getting Started with ${build.productName}**! - -Learn more about [${build.productName}](https://cryostat.io) from our website. -`, + link: { + href: build.homePageUrl, + text: 'cryostat.io', + }, type: { - text: 'Introduction', - color: 'green', + text: 'External', + color: 'purple', }, }, }; + +export default CryostatLinkQuickStart; diff --git a/src/app/QuickStarts/quickstarts/dashboard-quickstart.tsx b/src/app/QuickStarts/quickstarts/dashboard-quickstart.tsx new file mode 100644 index 000000000..18e8ad709 --- /dev/null +++ b/src/app/QuickStarts/quickstarts/dashboard-quickstart.tsx @@ -0,0 +1,178 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import cryostatLogoIcon from '@app/assets/cryostat_icon_rgb_default.svg'; +import cryostatLogo from '@app/assets/cryostat_logo_vert_rgb_default.svg'; +import build from '@app/build.json'; +import { FeatureLevel } from '@app/Shared/Services/Settings.service'; +import { QuickStart } from '@patternfly/quickstarts'; + +// TODO: Add quickstarts based on the following example: +const DashboardQuickStart: QuickStart = { + apiVersion: 'v2.3.0', + metadata: { + name: 'dashboard-quickstart', + featureLevel: FeatureLevel.PRODUCTION, + }, + spec: { + version: 2.3, + displayName: 'Dashboard', + durationMinutes: 10, + icon: cryostatLogoIcon, + description: `Learn about what you can do with the [APP] Dashboard.`, + prerequisites: [''], + introduction: ` +# Dashboard +The **[APP] Dashboard** is the main page of the [APP] console. The Cryostat Dashboard is able to provide a high-level overview of the [APP] instance and the target JVMs that it is connected to, through the use of **Dashboard Cards**. + +Dashboard Cards are displayed in the Dashboard in a grid layout. These **Dashboard Layouts** can be customized by adding, removing, or rearranging cards. The layout can also be saved and restored at a later time. + +Switch between layouts at any time for control over the information that is displayed in the Dashboard. + +Currently, the following Dashboard Cards are available: + +- **Target JVM Details** +- **Automated Analysis** +- **JFR Metrics Chart** +- **MBean Metrics Chart** + +Each card displays a different set of information about the currently selected target JVM, such as the heap usage, thread statistics, JVM vendor, and more. + +### What you'll learn +- How to create a simple Dashboard Layout +- How to add and remove a Dashboard Card to/from a Dashboard Layout +- How to move and resize Dashboard Cards +- How to rename, upload, download, and delete Dashboard Layouts + +[Learn more about each Dashboard Card in the [Cryostat documentation](${build.dashboardGuideUrl}).]{{admonition tip}} + `, + tasks: [ + { + title: 'Go to the Dashboard page', + description: `1. Click the [Dashboard]{{highlight nav-dashboard-tab}} tab in the [APP] console navigation bar.`, + review: { + instructions: '#### Verify that you see the Dashboard page.', + failedTaskHelp: + 'If you do not see the navigation bar, you can click the `☰` button in the [top left corner of the page]{{highlight nav-toggle-btn}}.', + }, + }, + { + title: 'Create a new Dashboard Layout', + description: ` +**Dashboard Layouts** are used to organize the Dashboard Cards that are displayed in the Dashboard. We will start by creating a new Dashboard Layout. + +1. Click [New]{{highlight dashboard-new-btn}} on the **Layout Selector** toolbar. + + This will open a modal dialog. + +2. Enter a name for the new layout. +3. Click Create when you are finished. +`, + review: { + instructions: '#### Verify that the new layout is created and selected with the name you entered.', + failedTaskHelp: + 'Make sure that the name you entered is unique, and contains only alphanumeric characters, underscores, dashes, and periods.', + }, + }, + { + title: 'Add Dashboard Cards to the Dashboard Layout', + description: ` +To create a card, we will go through a **creation wizard** that will guide us through the process of selecting the card type, and configuring the card, if needed. + +1. Click the [Add]{{highlight dashboard-add-btn}} button on the Dashboard. +2. From the [Card Type selector]{{highlight card-type-selector}}, select the **Target JVM Details** card. +3. Click Finish. +4. Repeat steps 1-2 to add the **MBeans Metrics Chart** card to the Dashboard Layout. +5. This time, click Next to go to the next configuration step of the creation wizard. +[The default metric selected for the card is the \`Process CPU Load\` metric. You can change this by clicking the **Performance Metric** dropdown menu within the **MBeans Chart Card** configuration step and selecting a different metric. Try other metrics and settings!]{{admonition tip}} +6. Click Finish again to finish creating the second card. +`, + review: { + instructions: '#### Verify that you see the two new cards in the Dashboard.', + failedTaskHelp: `If you do not see the new rule, follow the previous steps again.`, + }, + }, + { + title: 'Rearrange and resize Dashboard Cards', + description: ` +1. Click and drag the **Target JVM Details** [card's header]{{highlight card-draggable-grip}} on top or to the right of the **MBeans Metrics Chart** card to swap their positions. +2. Click and resize the **MBeans Metrics Chart** card to make it larger by dragging the right edge of the card. +[You can also drag and drop between cards to rearrange them.]{{admonition tip}} +`, + review: { + instructions: '#### Verify that you are able to rearrange and resize the cards in the Dashboard Layout.', + failedTaskHelp: + 'Make sure you are clicking and dragging the card header and not the card body to move the card. To resize cards, hover over the right edge of the card until the cursor changes to a double-sided arrow `↔`, then click and drag to resize.', + }, + }, + { + title: 'Modify Dashboard Layouts', + description: ` +You can rename, upload, download, and delete **Dashboard Layouts**. You may also quickly switch between them for different sets of information about the target JVMs. Customize these layouts to suit your needs! +1. Rename the current Dashboard Layout by clicking the [Pencil icon]{{highlight dashboard-rename-btn}} on the **Layout Selector** toolbar. +2. Download the current Dashboard Layout by clicking [Download]{{highlight dashboard-download-btn}} on the **Layout Selector** toolbar. +3. Delete the current Dashboard Layout by clicking [Delete]{{highlight dashboard-delete-btn}} on the **Layout Selector** toolbar. +4. Upload the Dashboard Layout that you downloaded in the previous step by clicking [Upload]{{highlight dashboard-upload-btn}} on the **Layout Selector** toolbar. + + This will open a modal dialog, where you can select the file you downloaded in the previous step. Press Submit and Close when you are finished with the modal. +5. To switch between Dashboard Layouts, click the [Layout Selector]{{highlight dashboard-layout-selector}} dropdown on the **Layout Selector** toolbar and select the \`Default\` layout. +[You can also favorite Dashboard Layouts by clicking on the [Layout Selector]{{highlight dashboard-layout-selector}} dropdown and clicking the Star Icon \`★\` next to the layout you want to favorite. Renaming and deletion can also be done in a similar fashion.]{{admonition tip}} +`, + review: { + instructions: + '#### Verify that you are able to rename, upload, download, delete, and switch between Dashboard Layouts.', + failedTaskHelp: + 'Make sure you are clicking the correct actions and buttons. If you are having trouble uploading a Dashboard Layout, make sure that the file you are uploading is a valid Dashboard Layout file.', + }, + }, + ], + conclusion: ` +
+

You completed the Dashboard quick start!

+
+ Cryostat Logo +
+

For more information about the new Dashboard and Dashboard Cards in [APP] 2.3, read our guides on the Cryostat documentation.

+
`, + type: { + text: 'Featured', + color: 'blue', + }, + }, +}; + +export default DashboardQuickStart; diff --git a/src/app/QuickStarts/quickstarts/generic-quickstart.tsx b/src/app/QuickStarts/quickstarts/generic-quickstart.tsx new file mode 100644 index 000000000..450774c7f --- /dev/null +++ b/src/app/QuickStarts/quickstarts/generic-quickstart.tsx @@ -0,0 +1,171 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import cryostatLogo from '@app/assets/cryostat_icon_rgb_default.svg'; +import build from '@app/build.json'; +import { FeatureLevel } from '@app/Shared/Services/Settings.service'; +import { QuickStart } from '@patternfly/quickstarts'; + +// Additional info: https://docs.openshift.com/container-platform/4.9/web_console/creating-quick-start-tutorials.html +const GenericQuickStart: QuickStart = { + apiVersion: 'v2.3.0', + metadata: { + name: 'generic-quickstart', + featureLevel: FeatureLevel.DEVELOPMENT, + // you can add additional metadata here + }, + spec: { + version: 2.3, // versioning for each release of the quick start + displayName: 'Getting started with quick starts', + durationMinutes: 10, + type: { + text: 'Type', + // 'blue' | 'cyan' | 'green' | 'orange' | 'purple' | 'red' | 'grey' + color: 'grey', + }, + /*- The icon defined as a base64 value. Example flow: + # 1. Find an .svg you want to use, like from here: https://www.patternfly.org/v4/guidelines/icons/#all-icons + # 2. Upload the file here and encode it (output format - plain text): https://base64.guru/converter/encode/image + # 3. compose - `icon: data:image/svg+xml;base64,` + # - If empty string (icon: ''), will use a default rocket icon + # - If set to null (icon: ~) will not show an icon + */ + icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMzA4LjY0NyAyOTEuNjc5NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9PC9zdHlsZT48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudCIgeDE9IjE1NC4zMjM1IiB5MT0iOS4xNjQ4NyIgeDI9IjE1NC4zMjM1IiB5Mj0iMjkyLjY2MzgzIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNTRlZWZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMWM3NWJjIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHRpdGxlPkFzc2V0IDE8L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTU0LjY3NDMuNTgxNmwuMDI4OC0uMjY3Ni0uMTMxMy4xNjQxLS4wNTMyLS4wNTA4TDE1NC41NjQ5LDBsLS4yNDA3LjIzNDlMMTU0LjA4MTUsMGwuMDQ1LjQyNzMtLjA1MDguMDUwOEwxNTMuOTQzOC4zMTRsLjAyODQuMjY3NkwwLDE1MS4wOTA5bDYwLjk3OSw4Ni4zODUyLDQ3LjEwMy0xMC44MTU5LDQ2LjI0MjIsNjUuMDE5NSw0Ni4yNDI3LTY1LjAxOTUsNDcuMTAzLDEwLjgxNTksMzcuNzcyLTUzLjUwOTcsMjMuMjA1MS0zMi44NzU1Wm0tNS4wMjY0LDE3LjY2OTlMMTI4LjYxNTIsMjE1LjMzNyw4Ny41MTY2LDE1OS4yODA4Wk04NS41MDA1LDE1Ni41MzM3LDU5LjYzNDgsMTIxLjI1NDRsODkuNzktMTA5LjgyMTdabTQyLjIyNzUsNjIuNTg2LTE4LjM4NDcsNC4yMTk3TDc4LjQzOSwxNzkuODg4N2w3LjcwMzEtMTcuNDg1M1pNMTUzLjgzODQsOC43MzkzbC40ODU4LTEuMTAyMS40ODQ0LDEuMDk2NywyMS43NDc1LDIxMS44OTUtLjA2NjgtLjA0NDktMjEuMzA2MiwzMS40ODc4LTIzLjA1NTctMzEuNTMyMi0uMDI4My4wMjA1Wm0yNi4xOTMzLDIwNi41OTk2TDE1OC45OTk1LDE4LjI0NTZsNjIuMTMwOSwxNDEuMDM1MlptNDIuNDc1MS01Mi45MzU1LDcuNzAzNiwxNy40ODUzLTMwLjkwNDcsNDMuNDUwNy0xOC4zODUzLTQuMjE5N1pNMTU5LjIyMzYsMTEuNDM3MWw4OS43ODg2LDEwOS44MTczLTI1Ljg2MzMsMzUuMjc5M1pNNjIuMjUsMjM0LjE1MzksMy44NjQ3LDE1MS40NDQ0LDEzOS43MTk3LDE4LjY0MTYsNTUuOTAxNCwxMjEuMTU2OGwyOC4yMjYsMzguNDk0Nkw3NS4wNjE1LDE4MC4yMzNsMzEuMTY2NSw0My44MjMyWm05Mi4wNzQyLDUyLjQzMjYtNDMuMTMwNC02MC42NDIxLDE4LjQzNTEtNC4yMzI5LjMxMi40MjU3LS4xOTgyLjE0NDYsMjUuNTMxMiwzNC45MTg0LDIzLjY2MTItMzQuOTU5OS0uMjAyNy0uMTM2Ny4yODcxLS4zOTIxLDE4LjQzNDEsNC4yMzI5Wk0yODMuMDMwOCwxODIuMjY0MmwtMzYuNjMyOSw1MS44ODk3LTQzLjk3NjUtMTAuMDk3NywzMS4xNjU1LTQzLjgyMzItOS4wNjU0LTIwLjU4MTYsMjguMjI2NS0zOC40OTQ2TDE2OC45Mjk3LDE4LjY0NDEsMzA0Ljc4MjIsMTUxLjQ0NDRaIi8+PC9nPjwvZz48L3N2Zz4=', + // icon: 'cryostatLogo', <- typically use this + prerequisites: [ + 'You can optionally list prerequisites', + 'Another prerequisite', + 'These prerequisites are also displayed on the introduction step', + ], + description: `This description appears on the card in the quick starts catalog.`, + // NOTE: markdown par will acknowledge indents and new lines + introduction: ` +**This introduction is shown at the beginning of the quick start** +- It introduces the quick start and lists the tasks within it. +- You can view the [source for this quick start](https://github.com/patternfly/patternfly-quickstarts/blob/main/packages/dev/src/quickstarts-data/yaml/template.yaml) for additional help and information.`, + tasks: [ + { + title: 'Get started', + description: ` +## Text + 1. The main body of the task. You can use markdown syntax here to create list items and more. + + This is a paragraph. + This is another paragraph. Add an empty line between paragraphs for line breaks or two spaces at the end. + 1. For more information on markdown syntax you can visit [this resource](https://www.markdownguide.org/basic-syntax/). + 1. A limited set of HTML tags [are also supported](https://docs.openshift.com/container-platform/4.9/web_console/creating-quick-start-tutorials.html#supported-tags-for-quick-starts_creating-quick-start-tutorials) + +## Images + HTML img tag: Cryostat logo + + > Markdown would work as well but cannot add height/width style + + Ellipsis icon (visible if font-awesome is installed): + + PF icon: + +## Highlighting + To enable highlighting, the markdown syntax should contain: + - Bracketed link text + - The highlight keyword, followed by the ID of the element that you want to animate + - The element to be highlighted, needs a \`data-quickstart-id\` attribute + +**Example** +
[Recordings nav item]{{highlight nav-recordings-tab}}
+ + will highlight an element with the \`data-quickstart-id="quickstarts"\` attribute + +### Code snippets +The syntax for an inline code snippet contains: +- Text between back quotes, followed by \`{{copy}}\` +#### Example 1 +The following text demonstates an inline-copy element \`https://github.com/sclorg/ruby-ex.git\`{{copy}} +#### Example 2 +And another one \`https://patternfly.org\`{{copy}} here! +The syntax for multi-line code snippets: +- Text between triple back quotes, followed by \`{{copy}}\` +#### Example 1 + \`\`\` +oc new-app ruby~https://github.com/sclorg/ruby-ex.git +echo "Expose route using oc expose svc/ruby-ex" +oc expose svc/ruby-ex + \`\`\`{{copy}} +#### Example 2 +\`\`\` +Hello +world +\`\`\`{{copy}} + - Clicking the _Next_ button will display the **Check your work** module. +### Admonition blocks + The syntax for rendering "Admonition Blocks" to Patternfly React Alerts: + - Bracketed alert text contents + - The admonition keyword, followed by the alert variant you want + - Variants are: note, tip, important, caution, and warning + +**Examples** + [This is the note contents with **some bold** text]{{admonition note}} + [This is the tip contents]{{admonition tip}} + [This is the important contents]{{admonition important}} + [This is the caution contents]{{admonition caution}} + [This is the warning contents]{{admonition warning}} + `, + // optional - the task's Check your work module + review: { + instructions: `Did you complete the task successfully?`, + failedTaskHelp: `This task isn't verified yet. Try the task again.`, + // optional - the task's success and failure messages + }, + summary: { + success: 'Shows a success message in the task header', + failed: 'Shows a failed message in the task header', + }, + }, + ], + conclusion: ` +
+

You completed the Getting started with quick starts quick start!

+ +
+ Cryostat Logo +
+

To learn more about [APP]'s extensive features and capabilities, read our guides at cryostat.io.

+
`, + nextQuickStart: ['start-a-recording-quickstart'], + }, +}; + +export default GenericQuickStart; diff --git a/src/app/QuickStarts/quickstarts/settings-quickstart.tsx b/src/app/QuickStarts/quickstarts/settings-quickstart.tsx new file mode 100644 index 000000000..542c0466b --- /dev/null +++ b/src/app/QuickStarts/quickstarts/settings-quickstart.tsx @@ -0,0 +1,155 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import cryostatLogo from '@app/assets/cryostat_logo_vert_rgb_default.svg'; +import build from '@app/build.json'; +import { FeatureLevel } from '@app/Shared/Services/Settings.service'; +import { QuickStart } from '@patternfly/quickstarts'; +import { CogIcon } from '@patternfly/react-icons'; +import React from 'react'; + +// TODO: Change this when adding dark mode setting to the settings page (General tab) +const SettingsQuickStart: QuickStart = { + apiVersion: 'v2.3.0', + metadata: { + name: 'settings-quickstart', + featureLevel: FeatureLevel.PRODUCTION, + }, + spec: { + version: 2.3, + displayName: 'Using Settings', + durationMinutes: 5, + icon: , + description: `Learn about the settings page in [APP] and how to use it.`, + prerequisites: [''], + introduction: ` +
+

+

Using Settings

+ Cryostat has a settings page that allows you to configure the application. This quick start will show you how to use the settings page. +

+ There are various settings that can be configured: +

+
    +
  • Connectivity
  • +
  • Languages & Region
  • +
  • Notification & Messages
  • +
  • Dashboard
  • +
  • Advanced
  • +
+ We will go over each of these settings in detail. +

+
+ `, + tasks: [ + { + title: 'Navigate to the Settings page', + description: ` +1. Press the [Settings]{{highlight settings-link}} cog icon.`, + }, + { + title: 'Go to the Connectivity settings tab', + description: ` +The Connectivity tab allows you to configure the WebSocket connection the browser forms with the Cryostat backend. + +1. Go to the [Connectivity]{{highlight settings-connectivity-tab}} tab. +2. Configure the WebSocket Connection Debounce time. +3. Configure the Auto-Refresh period for content-views. +[To use the Auto-Refresh feature, you must have the [Auto-Refresh]{{highlight settings-connectivity-tab-auto-refresh}} checkbox enabled.]{{admonition tip}} +`, + }, + { + title: 'Go to to the Languages & Region settings tab', + description: ` +1. Go to the [Languages & Region]{{highlight settings-language®ion-tab}} tab +2. Configure the date locale and current timezone. +[Cryostat currently only supports English. We are planning on adding support for other languages in the future.]{{admonition note}}`, + }, + { + title: 'Go to the Notifications & Messages tab', + description: ` +The Notifications & Messages tab allows you to configure the notifications and deletion warnings that are displayed in the Cryostat UI. + +1. Go to the [Notifications & Messages]{{highlight settings-notifications&messages-tab}} tab. +2. Enable or disable notifications from various categories if you choose to. +3. Enable or disable deletion dialog warnings for various destructive actions. +[You can also control the maximum number of notifications that can be displayed at once.]{{admonition tip}} +`, + }, + { + title: 'Go to the Dashboard tab', + description: ` +The Dashboard tab allows you to configure settings for the various Dashboard Cards that you can add to the Dashboard. + +New to [APP] 2.3 is the Automated Analysis Recording Configuration card. This card starts a recording and then automatically starts an analysis on the recording. You can configure the recording that is started by this card. + +1. Go to the [Dashboard]{{highlight settings-dashboard-tab}} tab. +2. Configure the Automated Analysis Recording Configuration settings. +3. Configure the Dashboard Metrics Configuration settings. + +[When using the Automated Analysis Card, make sure the event template is able to be used with the target JVM.]{{admonition warning}} +[Setting both an infinite maximum size and age may result in an out of memory error during report generation.]{{admonition caution}} +`, + }, + { + title: 'Go to the Advanced tab', + description: ` +Cryostat has a few advanced settings that can be configured. These settings are not recommended for most users. +1. Go to the [Advanced]{{highlight settings-advanced-tab}} tab. + +Credentials are used to authenticate with the target JVMs that Cryostat communicates with. If you don't want these credentials to be stored in the Cryostat backend, you can choose to store them in a local session storage instead. + +2. Configure the Credentials Storage settings. +`, + }, + ], + conclusion: ` +
+

You completed the Using Settings quick start!

+
+ Cryostat Logo +
+

For more information about configuring Settings in Cryostat, read our guides on the Cryostat website.

+
`, + type: { + text: 'Featured', + color: 'blue', + }, + }, +}; + +export default SettingsQuickStart; diff --git a/src/app/QuickStarts/quickstarts/start-a-recording.tsx b/src/app/QuickStarts/quickstarts/start-a-recording.tsx new file mode 100644 index 000000000..3c8d5e194 --- /dev/null +++ b/src/app/QuickStarts/quickstarts/start-a-recording.tsx @@ -0,0 +1,201 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import cryostatLogoIcon from '@app/assets/cryostat_icon_rgb_default.svg'; +import cryostatLogo from '@app/assets/cryostat_logo_vert_rgb_default.svg'; +import build from '@app/build.json'; +import { FeatureLevel } from '@app/Shared/Services/Settings.service'; + +import { QuickStart } from '@patternfly/quickstarts'; + +const RecordingQuickStart: QuickStart = { + apiVersion: 'v2.3.0', + metadata: { + name: 'start-a-recording-quickstart', + featureLevel: FeatureLevel.PRODUCTION, + }, + spec: { + version: 2.3, + displayName: 'Start a Recording', + durationMinutes: 10, + icon: cryostatLogoIcon, + description: `Learn how to start a recording with Java Flight Recorder (JFR) with [APP].`, + prerequisites: [''], + introduction: ` +# Start a Recording +**Java Flight Recorder (JFR)** is a profiling tool that is built into the JVM. It allows you to record events that happen in the JVM and then analyze the recording to find performance issues. + +In this quick start, you will use [APP] to connect to a target JVM and start a recording of the target JVM's activity. You will then stop and download the recording to your local machine. Finally, you will view an automated analysis report of the recording with [APP]'s capabilities. + +### What you'll learn + +- How to start/stop a JFR recording on a target JVM +- How to download a recording from [APP] to your local machine +- How to view an automated analysis report of a recording with [APP]'s capabilities + +### What you'll need + +- A running instance of [APP] which has discovered at least one target JVM +- JMX auth credentials for the target JVM (if required) + +`, + tasks: [ + { + title: 'Go to the Recordings tab', + description: + '1. Click the [Recordings]{{highlight nav-recordings-tab}} tab in the [APP] console navigation bar.', + review: { + instructions: '#### Verify that you see the Recordings page.', + failedTaskHelp: + 'If you do not see the navigation bar, you can click the `☰` button in the [top left corner of the page]{{highlight nav-toggle-btn}}.', + }, + }, + { + title: 'Select a target JVM', + description: ` + +Select a target JVM from the list of available targets that [APP] has discovered. + +1. Click the [Target Select]{{highlight target-select}} dropdown menu. +2. Select a target from the list of available targets. + + +[If JMX Auth username and password is required, you will be prompted to enter them.]{{admonition note}}`, + review: { + instructions: '#### Verify that you can see the Recordings table.', + failedTaskHelp: 'If you do not see the table, try the steps again.', + }, + }, + { + title: 'Start a recording', + description: ` +There are two tabs within the Recordings page: + +[Active Recordings]{{highlight active-recordings-tab}} and [Archived Recordings]{{highlight archived-recordings-tab}}. + +Active recordings are recordings that only exist only within the target JVM. Archived recordings are recordings that have been saved from the target JVM and copied to Cryostat's storage volume. + +We will start a recording while on the Active tab. + +1. Click [Create]{{highlight recordings-create-btn}} to go to the Custom Flight Recording Form. +2. Enter a name for the recording in the [Name]{{highlight crf-name}} field. +3. Select the [Duration]{{highlight crf-duration}} for the recording. You can select CONTINUOUS to record until the recording is stopped. +4. Select the Events to record using the [Event Template]{{highlight template-selector}} selector. +5. Click [Create]{{highlight crf-create-btn}} to start the recording. + +After the creation of a recording, the recording will be displayed in the Active Recordings tab. You should be able to see the recording's name, start time, duration, state, and any attached labels. + +[You may also attach metadata labels to the recordings under the [Metadata]{{highlight crf-metadata-opt}} options or configure your custom recording further under the [Advanced]{{highlight crf-advanced-opt}} options.]{{admonition note}}`, + review: { + instructions: '#### Verify that you see the recording within the table.', + failedTaskHelp: 'If you do not see the recording, try the steps again.', + }, + }, + { + title: 'Stop a recording', + description: ` +Stopping a recording will cut off the recording at the time that the recording is stopped. + +1. Click the [checkbox]{{highlight active-recordings-checkbox}} ☐ next to the recording. +2. Click the [Stop]{{highlight recordings-stop-btn}} button to stop the recording.`, + review: { + instructions: '#### Verify that the STATE field of the recording has changed to STOPPED.', + failedTaskHelp: 'If you do not see the recording, try the **Start a recording** task again.', + }, + }, + { + title: 'Download a recording', + description: ` +Downloading a recording will save the recording to your local machine as a JFR file. You can then use **JDK Mission Control (JMC)** to analyze the recording. +1. Open the [kebab menu]{{highlight recording-kebab}} next to the recording that you want to download. +2. Click \`Download Recording\` to prompt your browser to open a dialog to save the recording to your local machine. +3. Choose what to do with the file. + `, + review: { + instructions: '#### Verify that you have downloaded the recording to your local machine.', + failedTaskHelp: 'If you do not see the recording, try the Start a recording task again.', + }, + }, + { + title: 'View an analysis report', + description: ` +[APP] is able to generate an **automated analysis report** of a recording. This report is the same report that you would get if you were to view an automated analysis report in **JDK Mission Control**. The **JMC** rules engine analyzes your recording and looks for common problems and assigns a severity score from 0 (no problem) to 100 (potentially severe problem). +1. Click the [kebab menu]{{highlight recording-kebab}} next to the recording that you want to view an analysis report for. +2. Click \`View Report ...\` to view an analysis report of the recording in a new tab. + +3. *Optional:* Right click on the page and select \`Save Page As...\` to download the report HTML file to your local machine. +`, + review: { + instructions: '#### Verify that you can see an analysis report of the recording.', + failedTaskHelp: + 'The kebab icon `⁝` should be on the right end of the recording row in the active recordings table. Clicking the kebab icon should show a menu with the `View Report ...` option.', + }, + }, + { + title: 'Archive a recording', + description: ` +Archiving a recording will save the recording to [APP]'s archival storage, and will persist even after either the target JVM, or [APP], has stopped. These recordings will show up in the target JVM's Archived Recordings tab, as well as in the [Archives]{{highlight nav-archives-tab}} view on the [APP] console navigation bar. + +1. Click the [Archive]{{highlight recordings-archive-btn}} button to archive the recording. +2. Go to the [Archived Recordings]{{highlight archived-recordings-tab}} tab to see the archived recording in [APP]'s storage. + +[You can download archived recordings and view an analysis report of the archived recording from the [Archived Recordings]{{highlight archived-recordings-tab}} tab, similar to active recordings.]{{admonition tip}}`, + review: { + instructions: '#### Verify that the recording has been archived in the Archived Recordings tab.', + failedTaskHelp: + 'The recording name should have been saved like `__.jfr`. If you still do not see the recording, try the proceeding tasks again.', + }, + }, + ], + conclusion: ` +
+

You completed the Start a Recording quick start!

+ +
+ Cryostat Logo +
+

To learn more about [APP]'s extensive features and capabilities, please visit our website at ${build.documentationUrl}.

+
`, + type: { + text: 'Featured', + color: 'blue', + }, + nextQuickStart: ['automated-rules-quickstart'], + }, +}; + +export default RecordingQuickStart; diff --git a/src/app/Recordings/ActiveRecordingsTable.tsx b/src/app/Recordings/ActiveRecordingsTable.tsx index 27713ccc6..3dc64de67 100644 --- a/src/app/Recordings/ActiveRecordingsTable.tsx +++ b/src/app/Recordings/ActiveRecordingsTable.tsx @@ -58,7 +58,7 @@ import { NO_TARGET } from '@app/Shared/Services/Target.service'; import { useDayjs } from '@app/utils/useDayjs'; import { useSort } from '@app/utils/useSort'; import { useSubscriptions } from '@app/utils/useSubscriptions'; -import { sortResouces } from '@app/utils/utils'; +import { sortResources } from '@app/utils/utils'; import { Button, Checkbox, @@ -346,7 +346,7 @@ export const ActiveRecordingsTable: React.FunctionComponent { setFilteredRecordings( - sortResouces(sortBy, filterRecordings(recordings, targetRecordingFilters), mapper, getTransform) + sortResources(sortBy, filterRecordings(recordings, targetRecordingFilters), mapper, getTransform) ); }, [sortBy, recordings, targetRecordingFilters, setFilteredRecordings]); @@ -663,7 +663,7 @@ const ActiveRecordingsToolbar: React.FunctionComponent + ), @@ -682,6 +682,7 @@ const ActiveRecordingsToolbar: React.FunctionComponent {props.actionLoadings['ARCHIVE'] ? 'Archiving' : 'Archive'} @@ -699,7 +700,12 @@ const ActiveRecordingsToolbar: React.FunctionComponent + ), @@ -716,6 +722,7 @@ const ActiveRecordingsToolbar: React.FunctionComponent {props.actionLoadings['STOP'] ? 'Stopping' : 'Stop'} @@ -734,6 +741,7 @@ const ActiveRecordingsToolbar: React.FunctionComponent {props.actionLoadings['DELETE'] ? 'Deleting' : 'Delete'} @@ -890,6 +898,7 @@ export const ActiveRecordingRow: React.FC = ({ onChange={handleCheck} isChecked={checkedIndices.includes(index)} id={`active-table-row-${index}-check`} + data-quickstart-id="active-recordings-checkbox" /> = ( React.useEffect(() => { setFilteredRecordings( - sortResouces(sortBy, filterRecordings(recordings, targetRecordingFilters), mapper, getTransform) + sortResources(sortBy, filterRecordings(recordings, targetRecordingFilters), mapper, getTransform) ); }, [sortBy, recordings, targetRecordingFilters, setFilteredRecordings]); diff --git a/src/app/Recordings/RecordingActions.tsx b/src/app/Recordings/RecordingActions.tsx index 9258cb2ad..701f777b0 100644 --- a/src/app/Recordings/RecordingActions.tsx +++ b/src/app/Recordings/RecordingActions.tsx @@ -140,11 +140,11 @@ export const RecordingActions: React.FunctionComponent = menuAppendTo={document.body} position="right" direction="down" - toggle={} + toggle={} isPlain isOpen={isOpen} dropdownItems={actionItems.map((action) => ( - onSelect(action)}> + onSelect(action)} data-quickstart-id={action.key}> {action.title} ))} diff --git a/src/app/Recordings/Recordings.tsx b/src/app/Recordings/Recordings.tsx index 1290d8354..42153ffdb 100644 --- a/src/app/Recordings/Recordings.tsx +++ b/src/app/Recordings/Recordings.tsx @@ -74,10 +74,20 @@ export const Recordings: React.FC, Sta const cardBody = React.useMemo(() => { return archiveEnabled ? ( - Active Recordings}> + Active Recordings} + data-quickstart-id="active-recordings-tab" + > - Archived Recordings}> + Archived Recordings} + data-quickstart-id="archived-recordings-tab" + > diff --git a/src/app/Rules/CreateRule.tsx b/src/app/Rules/CreateRule.tsx index 7543c661b..54328ef10 100644 --- a/src/app/Rules/CreateRule.tsx +++ b/src/app/Rules/CreateRule.tsx @@ -330,6 +330,7 @@ const CreateRuleForm: React.FC = ({ ...props }) => { helperText="Enter a rule name." helperTextInvalid="A rule name may only contain letters, numbers, and underscores." validated={nameValid} + data-quickstart-id="rule-name" > = ({ ...props }) => { label="Description" fieldId="rule-description" helperText="Enter a rule description. This is only used for display purposes to aid in identifying rules and their intentions." + data-quickstart-id="rule-description" >