mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] [Platform] Data sources guided tour (#138327)
## Summary Adds a guided tour for data sources
This commit is contained in:
parent
2c96643043
commit
81d7a6f9ce
7 changed files with 149 additions and 23 deletions
|
@ -427,11 +427,17 @@ export const RULES_TABLE_MAX_PAGE_SIZE = 100;
|
|||
export const RULES_TABLE_PAGE_SIZE_OPTIONS = [5, 10, 20, 50, RULES_TABLE_MAX_PAGE_SIZE];
|
||||
|
||||
/**
|
||||
* A local storage key we use to store the state of the feature tour UI for the Rule Management page.
|
||||
* Local storage keys we use to store the state of our new features tours we currently show in the app.
|
||||
*
|
||||
* NOTE: As soon as we want to show a new tour for features in the current Kibana version,
|
||||
* we will need to update this constant with the corresponding version.
|
||||
* NOTE: As soon as we want to show tours for new features in the upcoming release,
|
||||
* we will need to update these constants with the corresponding version.
|
||||
*/
|
||||
export const NEW_FEATURES_TOUR_STORAGE_KEYS = {
|
||||
RULE_MANAGEMENT_PAGE: 'securitySolution.rulesManagementPage.newFeaturesTour.v8.4',
|
||||
RULE_CREATION_PAGE_DEFINE_STEP:
|
||||
'securitySolution.ruleCreationPage.defineStep.newFeaturesTour.v8.4',
|
||||
};
|
||||
|
||||
export const RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY =
|
||||
'securitySolution.rulesManagementPage.newFeaturesTour.v8.4';
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import type { UrlObject } from 'url';
|
|||
import Url from 'url';
|
||||
|
||||
import type { ROLES } from '../../common/test';
|
||||
import { RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY } from '../../common/constants';
|
||||
import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../../common/constants';
|
||||
import { TIMELINE_FLYOUT_BODY } from '../screens/timeline';
|
||||
import { hostDetailsUrl, LOGOUT_URL, userDetailsUrl } from '../urls/navigation';
|
||||
|
||||
|
@ -287,18 +287,20 @@ export const getEnvAuth = (): User => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Saves in localStorage rules feature tour config with deactivated option
|
||||
* It prevents tour to appear during tests and cover UI elements
|
||||
* For all the new features tours we show in the app, this method disables them
|
||||
* by setting their configs in the local storage. It prevents the tours from appearing
|
||||
* on the page during test runs and covering other UI elements.
|
||||
* @param window - browser's window object
|
||||
*/
|
||||
const disableFeatureTourForRuleManagementPage = (window: Window) => {
|
||||
const disableNewFeaturesTours = (window: Window) => {
|
||||
const tourStorageKeys = Object.values(NEW_FEATURES_TOUR_STORAGE_KEYS);
|
||||
const tourConfig = {
|
||||
isTourActive: false,
|
||||
};
|
||||
window.localStorage.setItem(
|
||||
RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY,
|
||||
JSON.stringify(tourConfig)
|
||||
);
|
||||
|
||||
tourStorageKeys.forEach((key) => {
|
||||
window.localStorage.setItem(key, JSON.stringify(tourConfig));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -326,7 +328,7 @@ export const visit = (
|
|||
if (onBeforeLoadCallback) {
|
||||
onBeforeLoadCallback(win);
|
||||
}
|
||||
disableFeatureTourForRuleManagementPage(win);
|
||||
disableNewFeaturesTours(win);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -334,20 +336,20 @@ export const visit = (
|
|||
|
||||
export const visitWithoutDateRange = (url: string, role?: ROLES) => {
|
||||
cy.visit(role ? getUrlWithRoute(role, url) : url, {
|
||||
onBeforeLoad: disableFeatureTourForRuleManagementPage,
|
||||
onBeforeLoad: disableNewFeaturesTours,
|
||||
});
|
||||
};
|
||||
|
||||
export const visitWithUser = (url: string, user: User) => {
|
||||
cy.visit(constructUrlWithUser(user, url), {
|
||||
onBeforeLoad: disableFeatureTourForRuleManagementPage,
|
||||
onBeforeLoad: disableNewFeaturesTours,
|
||||
});
|
||||
};
|
||||
|
||||
export const visitTimeline = (timelineId: string, role?: ROLES) => {
|
||||
const route = `/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`;
|
||||
cy.visit(role ? getUrlWithRoute(role, route) : route, {
|
||||
onBeforeLoad: disableFeatureTourForRuleManagementPage,
|
||||
onBeforeLoad: disableNewFeaturesTours,
|
||||
});
|
||||
cy.get('[data-test-subj="headerGlobalNav"]');
|
||||
cy.get(TIMELINE_FLYOUT_BODY).should('be.visible');
|
||||
|
|
|
@ -80,6 +80,7 @@ import { getIsRulePreviewDisabled } from '../rule_preview/helpers';
|
|||
import { NewTermsFields } from '../new_terms_fields';
|
||||
import { ScheduleItem } from '../schedule_item_form';
|
||||
import { DocLink } from '../../../../common/components/links_to_docs/doc_link';
|
||||
import { StepDefineRuleNewFeaturesTour } from './new_features_tour';
|
||||
|
||||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
|
@ -511,10 +512,15 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
/>
|
||||
);
|
||||
}, [kibanaDataViews]);
|
||||
|
||||
const DataSource = useMemo(() => {
|
||||
return (
|
||||
<RuleTypeEuiFormRow label={i18n.SOURCE} $isVisible={true} fullWidth>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<RuleTypeEuiFormRow id="dataSourceSelector" label={i18n.SOURCE} $isVisible={true} fullWidth>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="s"
|
||||
data-test-subj="dataViewIndexPatternButtonGroupFlexGroup"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="xs">
|
||||
<FormattedMessage
|
||||
|
@ -582,11 +588,11 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
);
|
||||
}, [
|
||||
dataSourceType,
|
||||
onChangeDataSource,
|
||||
dataViewIndexPatternToggleButtonOptions,
|
||||
DataViewSelectorMemo,
|
||||
indexModified,
|
||||
handleResetIndices,
|
||||
onChangeDataSource,
|
||||
]);
|
||||
|
||||
const QueryBarMemo = useMemo(
|
||||
|
@ -679,6 +685,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
) : (
|
||||
<>
|
||||
<StepContentWrapper addPadding={!isUpdateView}>
|
||||
<StepDefineRuleNewFeaturesTour />
|
||||
<Form form={form} data-test-subj="stepDefineRule">
|
||||
<StyledVisibleContainer isVisible={false}>
|
||||
<UseField
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { noop } from 'lodash';
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { EuiTourState } from '@elastic/eui';
|
||||
import { EuiSpacer, EuiTourStep, useEuiTour } from '@elastic/eui';
|
||||
|
||||
import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../../../../../common/constants';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
const TOUR_STORAGE_KEY = NEW_FEATURES_TOUR_STORAGE_KEYS.RULE_CREATION_PAGE_DEFINE_STEP;
|
||||
const TOUR_POPOVER_WIDTH = 300;
|
||||
|
||||
const tourConfig: EuiTourState = {
|
||||
currentTourStep: 1,
|
||||
isTourActive: true,
|
||||
tourPopoverWidth: TOUR_POPOVER_WIDTH,
|
||||
tourSubtitle: '',
|
||||
};
|
||||
|
||||
const stepsConfig = [
|
||||
{
|
||||
step: 1,
|
||||
title: i18n.DATA_SOURCE_GUIDE_TITLE,
|
||||
content: (
|
||||
<span>
|
||||
<p>{i18n.DATA_SOURCE_GUIDE_CONTENT}</p>
|
||||
<EuiSpacer />
|
||||
</span>
|
||||
),
|
||||
anchor: `#dataSourceSelector`,
|
||||
anchorPosition: 'rightCenter' as const,
|
||||
stepsTotal: 1,
|
||||
onFinish: noop,
|
||||
},
|
||||
];
|
||||
|
||||
export const StepDefineRuleNewFeaturesTour: FC = () => {
|
||||
const { storage } = useKibana().services;
|
||||
|
||||
const restoredState = useMemo<EuiTourState>(
|
||||
() => ({
|
||||
...tourConfig,
|
||||
...storage.get(TOUR_STORAGE_KEY),
|
||||
}),
|
||||
[storage]
|
||||
);
|
||||
|
||||
const [tourSteps, , tourState] = useEuiTour(stepsConfig, restoredState);
|
||||
|
||||
useEffect(() => {
|
||||
const { isTourActive, currentTourStep } = tourState;
|
||||
storage.set(TOUR_STORAGE_KEY, { isTourActive, currentTourStep });
|
||||
}, [tourState, storage]);
|
||||
|
||||
const [shouldShowTour, setShouldShowTour] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Wait until the tour target elements are visible on the page and mount
|
||||
* EuiTourStep components only after that. Otherwise, the tours would never
|
||||
* show up on the page.
|
||||
*/
|
||||
const observer = new MutationObserver(() => {
|
||||
if (document.querySelector(stepsConfig[0].anchor)) {
|
||||
setShouldShowTour(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return shouldShowTour ? <EuiTourStep {...tourSteps[0]} /> : null;
|
||||
};
|
|
@ -99,6 +99,27 @@ export const SOURCE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const DATA_SOURCE_GUIDE_SUB_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detections.dataSource.popover.title',
|
||||
{
|
||||
defaultMessage: 'Select a data source',
|
||||
}
|
||||
);
|
||||
|
||||
export const DATA_SOURCE_GUIDE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detections.dataSource.popover.subTitle',
|
||||
{
|
||||
defaultMessage: 'Data sources',
|
||||
}
|
||||
);
|
||||
|
||||
export const DATA_SOURCE_GUIDE_CONTENT = i18n.translate(
|
||||
'xpack.securitySolution.detections.dataSource.popover.content',
|
||||
{
|
||||
defaultMessage: 'Rules can now query index patterns or data views.',
|
||||
}
|
||||
);
|
||||
|
||||
export const RULE_PREVIEW_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle',
|
||||
{
|
||||
|
|
|
@ -16,12 +16,12 @@ New features and fixes to track:
|
|||
|
||||
## How to revive this tour for the next release (if needed)
|
||||
|
||||
1. Update Kibana version in `RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY`.
|
||||
1. Update Kibana version in `NEW_FEATURES_TOUR_STORAGE_KEYS.RULE_MANAGEMENT_PAGE`.
|
||||
Set it to a version you're going to implement a feature tour for.
|
||||
|
||||
2. Define the steps for your tour. See `RulesFeatureTour` and `stepsConfig`.
|
||||
|
||||
3. Define and set an anchor `id` for every step's target HTML element.
|
||||
3. Define and set an anchor `id` for every step's target HTML element.
|
||||
|
||||
4. Render `RulesFeatureTour` component somewhere on the Rule Management page.
|
||||
Only one instance of that component should be present on the page.
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
import { noop } from 'lodash';
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY } from '../../../../../../../common/constants';
|
||||
import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../../../../../../../common/constants';
|
||||
import { useKibana } from '../../../../../../common/lib/kibana';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -34,6 +34,7 @@ export interface RulesFeatureTourContextType {
|
|||
|
||||
export const SEARCH_CAPABILITIES_TOUR_ANCHOR = 'search-capabilities-tour-anchor';
|
||||
|
||||
const TOUR_STORAGE_KEY = NEW_FEATURES_TOUR_STORAGE_KEYS.RULE_MANAGEMENT_PAGE;
|
||||
const TOUR_POPOVER_WIDTH = 400;
|
||||
|
||||
const tourConfig: EuiTourState = {
|
||||
|
@ -61,7 +62,7 @@ export const RulesFeatureTour: FC = () => {
|
|||
const restoredState = useMemo<EuiTourState>(
|
||||
() => ({
|
||||
...tourConfig,
|
||||
...storage.get(RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY),
|
||||
...storage.get(TOUR_STORAGE_KEY),
|
||||
}),
|
||||
[storage]
|
||||
);
|
||||
|
@ -70,7 +71,7 @@ export const RulesFeatureTour: FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
const { isTourActive, currentTourStep } = tourState;
|
||||
storage.set(RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY, { isTourActive, currentTourStep });
|
||||
storage.set(TOUR_STORAGE_KEY, { isTourActive, currentTourStep });
|
||||
}, [tourState, storage]);
|
||||
|
||||
const [shouldShowSearchCapabilitiesTour, setShouldShowSearchCapabilitiesTour] = useState(false);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue