Skip to content

Commit

Permalink
[Security Solution] [Platform] Data sources guided tour (#138327)
Browse files Browse the repository at this point in the history
## Summary

Adds a guided tour for data sources
  • Loading branch information
dhurley14 authored Aug 15, 2022
1 parent 2c96643 commit 81d7a6f
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 23 deletions.
12 changes: 9 additions & 3 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
26 changes: 14 additions & 12 deletions x-pack/plugins/security_solution/cypress/tasks/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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));
});
};

/**
Expand Down Expand Up @@ -326,28 +328,28 @@ export const visit = (
if (onBeforeLoadCallback) {
onBeforeLoadCallback(win);
}
disableFeatureTourForRuleManagementPage(win);
disableNewFeaturesTours(win);
},
}
);
};

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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -582,11 +588,11 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
);
}, [
dataSourceType,
onChangeDataSource,
dataViewIndexPatternToggleButtonOptions,
DataViewSelectorMemo,
indexModified,
handleResetIndices,
onChangeDataSource,
]);

const QueryBarMemo = useMemo(
Expand Down Expand Up @@ -679,6 +685,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
) : (
<>
<StepContentWrapper addPadding={!isUpdateView}>
<StepDefineRuleNewFeaturesTour />
<Form form={form} data-test-subj="stepDefineRule">
<StyledVisibleContainer isVisible={false}>
<UseField
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 = {
Expand Down Expand Up @@ -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]
);
Expand All @@ -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);
Expand Down

0 comments on commit 81d7a6f

Please sign in to comment.