Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Transforms: Adds a link to discover from the transform list to the actions menu. #97805

Merged
merged 13 commits into from
Apr 27, 2021
Merged
3 changes: 2 additions & 1 deletion x-pack/plugins/transform/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"licensing",
"management",
"features",
"savedObjects"
"savedObjects",
"share"
],
"optionalPlugins": [
"security",
Expand Down
19 changes: 17 additions & 2 deletions x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,28 @@

import { useContext } from 'react';

import type { ScopedHistory } from 'kibana/public';

import { coreMock } from '../../../../../../src/core/public/mocks';
import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';
import { savedObjectsPluginMock } from '../../../../../../src/plugins/saved_objects/public/mocks';
import { SharePluginStart } from '../../../../../../src/plugins/share/public';

import { Storage } from '../../../../../../src/plugins/kibana_utils/public';

import type { AppDependencies } from '../app_dependencies';
import { MlSharedContext } from './shared_context';
import type { GetMlSharedImportsReturnType } from '../../shared_imports';

const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const dataStart = dataPluginMock.createStartContract();

const appDependencies = {
// Replace mock to support syntax using `.then()` as used in transform code.
coreStart.savedObjects.client.find = jest.fn().mockResolvedValue({ savedObjects: [] });

const appDependencies: AppDependencies = {
application: coreStart.application,
chrome: coreStart.chrome,
data: dataStart,
docLinks: coreStart.docLinks,
Expand All @@ -28,11 +39,15 @@ const appDependencies = {
storage: ({ get: jest.fn() } as unknown) as Storage,
overlays: coreStart.overlays,
http: coreSetup.http,
history: {} as ScopedHistory,
savedObjectsPlugin: savedObjectsPluginMock.createStartContract(),
share: ({ urlGenerators: { getUrlGenerator: jest.fn() } } as unknown) as SharePluginStart,
ml: {} as GetMlSharedImportsReturnType,
};

export const useAppDependencies = () => {
const ml = useContext(MlSharedContext);
return { ...appDependencies, ml, savedObjects: jest.fn() };
return { ...appDependencies, ml };
};

export const useToastNotifications = () => {
Expand Down
13 changes: 8 additions & 5 deletions x-pack/plugins/transform/public/app/app_dependencies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
* 2.0.
*/

import { CoreSetup, CoreStart } from 'src/core/public';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { SavedObjectsStart } from 'src/plugins/saved_objects/public';
import { ScopedHistory } from 'kibana/public';
import type { CoreSetup, CoreStart } from 'src/core/public';
import type { DataPublicPluginStart } from 'src/plugins/data/public';
import type { SavedObjectsStart } from 'src/plugins/saved_objects/public';
import type { ScopedHistory } from 'kibana/public';
import type { SharePluginStart } from 'src/plugins/share/public';

import { useKibana } from '../../../../../src/plugins/kibana_react/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import type { Storage } from '../../../../../src/plugins/kibana_utils/public';

import type { GetMlSharedImportsReturnType } from '../shared_imports';

export interface AppDependencies {
application: CoreStart['application'];
chrome: CoreStart['chrome'];
data: DataPublicPluginStart;
docLinks: CoreStart['docLinks'];
Expand All @@ -28,6 +30,7 @@ export interface AppDependencies {
overlays: CoreStart['overlays'];
history: ScopedHistory;
savedObjectsPlugin: SavedObjectsStart;
share: SharePluginStart;
ml: GetMlSharedImportsReturnType;
}

Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/transform/public/app/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export {
} from './transform';
export { TRANSFORM_LIST_COLUMN, TransformListAction, TransformListRow } from './transform_list';
export { getTransformProgress, isCompletedBatchTransform } from './transform_stats';
export { getDiscoverUrl } from './navigation';
export {
getEsAggFromAggConfig,
isPivotAggsConfigWithUiSupport,
Expand Down
16 changes: 0 additions & 16 deletions x-pack/plugins/transform/public/app/common/navigation.test.tsx

This file was deleted.

19 changes: 0 additions & 19 deletions x-pack/plugins/transform/public/app/common/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,9 @@

import React, { FC } from 'react';
import { Redirect } from 'react-router-dom';
import rison from 'rison-node';

import { SECTION_SLUG } from '../constants';

/**
* Gets a url for navigating to Discover page.
* @param indexPatternId Index pattern ID.
* @param baseUrl Base url.
*/
export function getDiscoverUrl(indexPatternId: string, baseUrl: string): string {
const _g = rison.encode({});

// Add the index pattern ID to the appState part of the URL.
const _a = rison.encode({
index: indexPatternId,
});

const hash = `/discover#?_g=${_g}&_a=${_a}`;

return `${baseUrl}/app${hash}`;
}

export const RedirectToTransformManagement: FC = () => <Redirect to={`/${SECTION_SLUG.HOME}`} />;

export const RedirectToCreateTransform: FC<{ savedObjectId: string }> = ({ savedObjectId }) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export async function mountManagementSection(
const { http, notifications, getStartServices } = coreSetup;
const startServices = await getStartServices();
const [core, plugins] = startServices;
const { chrome, docLinks, i18n, overlays, savedObjects, uiSettings } = core;
const { data } = plugins;
const { application, chrome, docLinks, i18n, overlays, savedObjects, uiSettings } = core;
const { data, share } = plugins;
const { docTitle } = chrome;

// Initialize services
Expand All @@ -39,6 +39,7 @@ export async function mountManagementSection(

// AppCore/AppPlugins to be passed on as React context
const appDependencies: AppDependencies = {
application,
chrome,
data,
docLinks,
Expand All @@ -51,6 +52,7 @@ export async function mountManagementSection(
uiSettings,
history,
savedObjectsPlugin: plugins.savedObjects,
share,
ml: await getMlSharedImports(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ import {

import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';

import {
DISCOVER_APP_URL_GENERATOR,
DiscoverUrlGeneratorState,
} from '../../../../../../../../../src/plugins/discover/public';

import type { PutTransformsResponseSchema } from '../../../../../../common/api_schemas/transforms';
import {
isGetTransformsStatsResponseSchema,
Expand All @@ -36,7 +41,7 @@ import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants

import { getErrorMessage } from '../../../../../../common/utils/errors';

import { getTransformProgress, getDiscoverUrl } from '../../../../common';
import { getTransformProgress } from '../../../../common';
import { useApi } from '../../../../hooks/use_api';
import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies';
import { RedirectToTransformManagement } from '../../../../common/navigation';
Expand Down Expand Up @@ -86,13 +91,45 @@ export const StepCreateForm: FC<StepCreateFormProps> = React.memo(
const [progressPercentComplete, setProgressPercentComplete] = useState<undefined | number>(
undefined
);
const [discoverLink, setDiscoverLink] = useState<string>();

const deps = useAppDependencies();
const indexPatterns = deps.data.indexPatterns;
const toastNotifications = useToastNotifications();
const { getUrlGenerator } = deps.share.urlGenerators;
const isDiscoverAvailable = deps.application.capabilities.discover?.show ?? false;

useEffect(() => {
let unmounted = false;

onChange({ created, started, indexPatternId });

const getDiscoverUrl = async (): Promise<void> => {
const state: DiscoverUrlGeneratorState = {
indexPatternId,
};

let discoverUrlGenerator;
try {
discoverUrlGenerator = getUrlGenerator(DISCOVER_APP_URL_GENERATOR);
} catch (error) {
// ignore error thrown when url generator is not available
return;
}

const discoverUrl = await discoverUrlGenerator.createUrl(state);
if (!unmounted) {
setDiscoverLink(discoverUrl);
}
};

if (started === true && indexPatternId !== undefined && isDiscoverAvailable) {
getDiscoverUrl();
}

return () => {
unmounted = true;
};
// custom comparison
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [created, started, indexPatternId]);
Expand Down Expand Up @@ -477,7 +514,7 @@ export const StepCreateForm: FC<StepCreateFormProps> = React.memo(
</EuiPanel>
</EuiFlexItem>
)}
{started === true && indexPatternId !== undefined && (
{isDiscoverAvailable && discoverLink !== undefined && (
<EuiFlexItem style={PANEL_ITEM_STYLE}>
<EuiCard
icon={<EuiIcon size="xxl" type="discoverApp" />}
Expand All @@ -490,7 +527,7 @@ export const StepCreateForm: FC<StepCreateFormProps> = React.memo(
defaultMessage: 'Use Discover to explore the transform.',
}
)}
href={getDiscoverUrl(indexPatternId, deps.http.basePath.get())}
href={discoverLink}
data-test-subj="transformWizardCardDiscover"
/>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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 { cloneDeep } from 'lodash';
import React from 'react';
import { IntlProvider } from 'react-intl';

import { render, waitFor, screen } from '@testing-library/react';

import { TransformListRow } from '../../../../common';
import { isDiscoverActionDisabled, DiscoverActionName } from './discover_action_name';

import transformListRow from '../../../../common/__mocks__/transform_list_row.json';

jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');

// @ts-expect-error mock data is too loosely typed
const item: TransformListRow = transformListRow;

describe('Transform: Transform List Actions isDiscoverActionDisabled()', () => {
it('should be disabled when more than one item is passed in', () => {
expect(isDiscoverActionDisabled([item, item], false, true)).toBe(true);
});
it('should be disabled when forceDisable is true', () => {
expect(isDiscoverActionDisabled([item], true, true)).toBe(true);
});
it('should be disabled when the index pattern is not available', () => {
expect(isDiscoverActionDisabled([item], false, false)).toBe(true);
});
it('should be disabled when the transform started but has no index pattern', () => {
const itemCopy = cloneDeep(item);
itemCopy.stats.state = 'started';
expect(isDiscoverActionDisabled([itemCopy], false, false)).toBe(true);
});
it('should be enabled when the transform started and has an index pattern', () => {
const itemCopy = cloneDeep(item);
itemCopy.stats.state = 'started';
expect(isDiscoverActionDisabled([itemCopy], false, true)).toBe(false);
});
it('should be enabled when the index pattern is available', () => {
expect(isDiscoverActionDisabled([item], false, true)).toBe(false);
});
});

describe('Transform: Transform List Actions <StopAction />', () => {
it('renders an enabled button', async () => {
// prepare
render(
<IntlProvider locale="en">
<DiscoverActionName items={[item]} indexPatternExists={true} />
</IntlProvider>
);

// assert
await waitFor(() => {
expect(
screen.queryByTestId('transformDiscoverActionNameText disabled')
).not.toBeInTheDocument();
expect(screen.queryByTestId('transformDiscoverActionNameText enabled')).toBeInTheDocument();
expect(screen.queryByText('View in Discover')).toBeInTheDocument();
});
});

it('renders a disabled button', async () => {
// prepare
const itemCopy = cloneDeep(item);
itemCopy.stats.checkpointing.last.checkpoint = 0;
render(
<IntlProvider locale="en">
<DiscoverActionName items={[itemCopy]} indexPatternExists={false} />
</IntlProvider>
);

// assert
await waitFor(() => {
expect(screen.queryByTestId('transformDiscoverActionNameText disabled')).toBeInTheDocument();
expect(
screen.queryByTestId('transformDiscoverActionNameText enabled')
).not.toBeInTheDocument();
expect(screen.queryByText('View in Discover')).toBeInTheDocument();
});
});
});
Loading