diff --git a/x-pack/plugins/transform/common/api_schemas/transforms.ts b/x-pack/plugins/transform/common/api_schemas/transforms.ts index 5b300a613be94..65e0433a81264 100644 --- a/x-pack/plugins/transform/common/api_schemas/transforms.ts +++ b/x-pack/plugins/transform/common/api_schemas/transforms.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema, type TypeOf } from '@kbn/config-schema'; import type { ES_FIELD_TYPES } from '@kbn/field-types'; diff --git a/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts b/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts index 459d37414e244..a9d177a341e21 100644 --- a/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts +++ b/x-pack/plugins/transform/common/api_schemas/transforms_stats.ts @@ -13,7 +13,7 @@ import { getTransformsRequestSchema } from './transforms'; export const getTransformsStatsRequestSchema = getTransformsRequestSchema; -export type GetTransformsRequestSchema = TypeOf; +export type GetTransformsStatsRequestSchema = TypeOf; export interface GetTransformsStatsResponseSchema { node_failures?: object; diff --git a/x-pack/plugins/transform/common/api_schemas/type_guards.ts b/x-pack/plugins/transform/common/api_schemas/type_guards.ts deleted file mode 100644 index d5dbad0056c35..0000000000000 --- a/x-pack/plugins/transform/common/api_schemas/type_guards.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - -import { isPopulatedObject } from '@kbn/ml-is-populated-object'; - -import type { EsIndex } from '../types/es_index'; -import type { EsIngestPipeline } from '../types/es_ingest_pipeline'; - -// To be able to use the type guards on the client side, we need to make sure we don't import -// the code of '@kbn/config-schema' but just its types, otherwise the client side code will -// fail to build. -import type { FieldHistogramsResponseSchema } from './field_histograms'; -import type { GetTransformsAuditMessagesResponseSchema } from './audit_messages'; -import type { DeleteTransformsResponseSchema } from './delete_transforms'; -import type { ResetTransformsResponseSchema } from './reset_transforms'; -import type { StartTransformsResponseSchema } from './start_transforms'; -import type { StopTransformsResponseSchema } from './stop_transforms'; -import type { ScheduleNowTransformsResponseSchema } from './schedule_now_transforms'; -import type { - GetTransformNodesResponseSchema, - GetTransformsResponseSchema, - PostTransformsPreviewResponseSchema, - PutTransformsResponseSchema, -} from './transforms'; -import type { GetTransformsStatsResponseSchema } from './transforms_stats'; -import type { PostTransformsUpdateResponseSchema } from './update_transforms'; - -const isGenericResponseSchema = (arg: any): arg is T => { - return isPopulatedObject(arg, ['count', 'transforms']) && Array.isArray(arg.transforms); -}; - -export const isGetTransformNodesResponseSchema = ( - arg: unknown -): arg is GetTransformNodesResponseSchema => { - return isPopulatedObject(arg, ['count']) && typeof arg.count === 'number'; -}; - -export const isGetTransformsResponseSchema = (arg: unknown): arg is GetTransformsResponseSchema => { - return isGenericResponseSchema(arg); -}; - -export const isGetTransformsStatsResponseSchema = ( - arg: unknown -): arg is GetTransformsStatsResponseSchema => { - return isGenericResponseSchema(arg); -}; - -export const isDeleteTransformsResponseSchema = ( - arg: unknown -): arg is DeleteTransformsResponseSchema => { - return ( - isPopulatedObject(arg) && - Object.values(arg).every((d) => isPopulatedObject(d, ['transformDeleted'])) - ); -}; - -export const isResetTransformsResponseSchema = ( - arg: unknown -): arg is ResetTransformsResponseSchema => { - return ( - isPopulatedObject(arg) && - Object.values(arg).every((d) => isPopulatedObject(d, ['transformReset'])) - ); -}; - -export const isEsIndices = (arg: unknown): arg is EsIndex[] => { - return Array.isArray(arg); -}; - -export const isEsIngestPipelines = (arg: unknown): arg is EsIngestPipeline[] => { - return Array.isArray(arg) && arg.every((d) => isPopulatedObject(d, ['name'])); -}; - -export const isEsSearchResponse = (arg: unknown): arg is estypes.SearchResponse => { - return isPopulatedObject(arg, ['hits']); -}; - -type SearchResponseWithAggregations = Required> & - estypes.SearchResponse; -export const isEsSearchResponseWithAggregations = ( - arg: unknown -): arg is SearchResponseWithAggregations => { - return isEsSearchResponse(arg) && {}.hasOwnProperty.call(arg, 'aggregations'); -}; - -export const isFieldHistogramsResponseSchema = ( - arg: unknown -): arg is FieldHistogramsResponseSchema => { - return Array.isArray(arg); -}; - -export const isGetTransformsAuditMessagesResponseSchema = ( - arg: unknown -): arg is GetTransformsAuditMessagesResponseSchema => { - return isPopulatedObject(arg, ['messages', 'total']); -}; - -export const isPostTransformsPreviewResponseSchema = ( - arg: unknown -): arg is PostTransformsPreviewResponseSchema => { - return ( - isPopulatedObject(arg, ['generated_dest_index', 'preview']) && - typeof arg.generated_dest_index !== undefined && - Array.isArray(arg.preview) - ); -}; - -export const isPostTransformsUpdateResponseSchema = ( - arg: unknown -): arg is PostTransformsUpdateResponseSchema => { - return isPopulatedObject(arg, ['id']) && typeof arg.id === 'string'; -}; - -export const isPutTransformsResponseSchema = (arg: unknown): arg is PutTransformsResponseSchema => { - return ( - isPopulatedObject(arg, ['transformsCreated', 'errors']) && - Array.isArray(arg.transformsCreated) && - Array.isArray(arg.errors) - ); -}; - -const isGenericSuccessResponseSchema = (arg: unknown) => - isPopulatedObject(arg) && Object.values(arg).every((d) => isPopulatedObject(d, ['success'])); - -export const isStartTransformsResponseSchema = ( - arg: unknown -): arg is StartTransformsResponseSchema => { - return isGenericSuccessResponseSchema(arg); -}; - -export const isStopTransformsResponseSchema = ( - arg: unknown -): arg is StopTransformsResponseSchema => { - return isGenericSuccessResponseSchema(arg); -}; - -export const isScheduleNowTransformsResponseSchema = ( - arg: unknown -): arg is ScheduleNowTransformsResponseSchema => { - return isGenericSuccessResponseSchema(arg); -}; diff --git a/x-pack/plugins/transform/common/constants.ts b/x-pack/plugins/transform/common/constants.ts index 633f0dc7849ac..a1ba2d8277af9 100644 --- a/x-pack/plugins/transform/common/constants.ts +++ b/x-pack/plugins/transform/common/constants.ts @@ -32,6 +32,21 @@ const EXTERNAL_API_BASE_PATH = '/api/transform/'; export const addInternalBasePath = (uri: string): string => `${INTERNAL_API_BASE_PATH}${uri}`; export const addExternalBasePath = (uri: string): string => `${EXTERNAL_API_BASE_PATH}${uri}`; +export const TRANSFORM_REACT_QUERY_KEYS = { + DATA_SEARCH: 'transform.data_search', + DATA_VIEW_EXISTS: 'transform.data_view_exists', + GET_DATA_VIEW_TITLES: 'transform.get_data_view_titles', + GET_ES_INDICES: 'transform.get_es_indices', + GET_ES_INGEST_PIPELINES: 'transform.get_es_ingest_pipelines', + GET_HISTOGRAMS_FOR_FIELDS: 'transform.get_histograms_for_fields', + GET_TRANSFORM: 'transform.get_transform', + GET_TRANSFORM_NODES: 'transform.get_transform_nodes', + GET_TRANSFORM_AUDIT_MESSAGES: 'transform.get_transform_audit_messages', + GET_TRANSFORM_STATS: 'transform.get_transform_stats', + GET_TRANSFORMS: 'transform.get_transforms', + GET_TRANSFORMS_PREVIEW: 'transform.get_transforms_preview', +} as const; + // In order to create a transform, the API requires the following privileges: // - transform_admin (builtin) // - cluster privileges: manage_transform @@ -71,22 +86,6 @@ export const APP_CLUSTER_PRIVILEGES = [ // Minimum privileges required to return transform node count export const NODES_INFO_PRIVILEGES = ['cluster:monitor/transform/get']; -// Equivalent of capabilities.canGetTransform -export const APP_GET_TRANSFORM_CLUSTER_PRIVILEGES = [ - 'cluster.cluster:monitor/transform/get', - 'cluster.cluster:monitor/transform/stats/get', -]; - -// Equivalent of capabilities.canCreateTransform -export const APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES = [ - 'cluster.cluster:monitor/transform/get', - 'cluster.cluster:monitor/transform/stats/get', - 'cluster.cluster:admin/transform/preview', - 'cluster.cluster:admin/transform/put', - 'cluster.cluster:admin/transform/start', - 'cluster.cluster:admin/transform/start_task', -]; - export const APP_INDEX_PRIVILEGES = ['monitor']; // reflects https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformStats.java#L214 diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts deleted file mode 100644 index 9dee0c1a73cf1..0000000000000 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; -import { isPopulatedObject } from '@kbn/ml-is-populated-object'; - -import { cloneDeep } from 'lodash'; -import { APP_INDEX_PRIVILEGES } from '../constants'; -import { Privileges } from '../types/privileges'; - -export interface PrivilegesAndCapabilities { - privileges: Privileges; - capabilities: Capabilities; -} - -export interface TransformCapabilities { - canGetTransform: boolean; - canDeleteTransform: boolean; - canPreviewTransform: boolean; - canCreateTransform: boolean; - canReauthorizeTransform: boolean; - canScheduleNowTransform: boolean; - canStartStopTransform: boolean; - canCreateTransformAlerts: boolean; - canUseTransformAlerts: boolean; - canResetTransform: boolean; -} -export type Capabilities = { [k in keyof TransformCapabilities]: boolean }; - -export const INITIAL_CAPABILITIES = Object.freeze({ - canGetTransform: false, - canDeleteTransform: false, - canPreviewTransform: false, - canCreateTransform: false, - canReauthorizeTransform: false, - canScheduleNowTransform: false, - canStartStopTransform: false, - canCreateTransformAlerts: false, - canUseTransformAlerts: false, - canResetTransform: false, -}); - -export type Privilege = [string, string]; - -function isPrivileges(arg: unknown): arg is Privileges { - return ( - isPopulatedObject(arg, ['hasAllPrivileges', 'missingPrivileges']) && - typeof arg.hasAllPrivileges === 'boolean' && - typeof arg.missingPrivileges === 'object' && - arg.missingPrivileges !== null - ); -} - -export const toArray = (value: string | string[]): string[] => - Array.isArray(value) ? value : [value]; - -export const hasPrivilegeFactory = - (privileges: Privileges | undefined | null) => (privilege: Privilege) => { - const [section, requiredPrivilege] = privilege; - if (isPrivileges(privileges) && !privileges.missingPrivileges[section]) { - // if the section does not exist in our missingPrivileges, everything is OK - return true; - } - if (isPrivileges(privileges) && privileges.missingPrivileges[section]!.length === 0) { - return true; - } - if (requiredPrivilege === '*') { - // If length > 0 and we require them all... KO - return false; - } - // If we require _some_ privilege, we make sure that the one - // we require is *not* in the missingPrivilege array - return ( - isPrivileges(privileges) && - !privileges.missingPrivileges[section]!.includes(requiredPrivilege) - ); - }; - -export const extractMissingPrivileges = ( - privilegesObject: { [key: string]: boolean } = {} -): string[] => - Object.keys(privilegesObject).reduce((privileges: string[], privilegeName: string): string[] => { - if (!privilegesObject[privilegeName]) { - privileges.push(privilegeName); - } - return privileges; - }, []); - -export const getPrivilegesAndCapabilities = ( - clusterPrivileges: Record, - hasOneIndexWithAllPrivileges: boolean, - hasAllPrivileges: boolean -): PrivilegesAndCapabilities => { - const privilegesResult: Privileges = { - hasAllPrivileges: true, - missingPrivileges: { - cluster: [], - index: [], - }, - }; - - // Find missing cluster privileges and set overall app privileges - privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(clusterPrivileges); - privilegesResult.hasAllPrivileges = hasAllPrivileges; - - if (!hasOneIndexWithAllPrivileges) { - privilegesResult.missingPrivileges.index = [...APP_INDEX_PRIVILEGES]; - } - - const hasPrivilege = hasPrivilegeFactory(privilegesResult); - - const capabilities = cloneDeep(INITIAL_CAPABILITIES); - capabilities.canGetTransform = - hasPrivilege(['cluster', 'cluster:monitor/transform/get']) && - hasPrivilege(['cluster', 'cluster:monitor/transform/stats/get']); - - capabilities.canCreateTransform = hasPrivilege(['cluster', 'cluster:admin/transform/put']); - - capabilities.canDeleteTransform = hasPrivilege(['cluster', 'cluster:admin/transform/delete']); - - capabilities.canResetTransform = hasPrivilege(['cluster', 'cluster:admin/transform/reset']); - - capabilities.canPreviewTransform = hasPrivilege(['cluster', 'cluster:admin/transform/preview']); - - capabilities.canStartStopTransform = - hasPrivilege(['cluster', 'cluster:admin/transform/start']) && - hasPrivilege(['cluster', 'cluster:admin/transform/start_task']) && - hasPrivilege(['cluster', 'cluster:admin/transform/stop']); - - capabilities.canCreateTransformAlerts = capabilities.canCreateTransform; - - capabilities.canUseTransformAlerts = capabilities.canGetTransform; - - capabilities.canScheduleNowTransform = capabilities.canStartStopTransform; - - capabilities.canReauthorizeTransform = capabilities.canStartStopTransform; - - return { privileges: privilegesResult, capabilities }; -}; -// create the text for button's tooltips if the user -// doesn't have the permission to press that button -export function createCapabilityFailureMessage( - capability: keyof TransformCapabilities | 'noTransformNodes' -) { - let message = ''; - - switch (capability) { - case 'canCreateTransform': - message = i18n.translate('xpack.transform.capability.noPermission.createTransformTooltip', { - defaultMessage: 'You do not have permission to create transforms.', - }); - break; - case 'canCreateTransformAlerts': - message = i18n.translate( - 'xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip', - { - defaultMessage: 'You do not have permission to create transform alert rules.', - } - ); - break; - case 'canScheduleNowTransform': - message = i18n.translate( - 'xpack.transform.capability.noPermission.scheduleNowTransformTooltip', - { - defaultMessage: - 'You do not have permission to schedule transforms to process data instantly.', - } - ); - break; - case 'canStartStopTransform': - message = i18n.translate( - 'xpack.transform.capability.noPermission.startOrStopTransformTooltip', - { - defaultMessage: 'You do not have permission to start or stop transforms.', - } - ); - break; - - case 'canReauthorizeTransform': - message = i18n.translate( - 'xpack.transform.capability.noPermission.reauthorizeTransformTooltip', - { - defaultMessage: 'You do not have permission to reauthorize transforms.', - } - ); - break; - - case 'canDeleteTransform': - message = i18n.translate('xpack.transform.capability.noPermission.deleteTransformTooltip', { - defaultMessage: 'You do not have permission to delete transforms.', - }); - break; - - case 'canResetTransform': - message = i18n.translate('xpack.transform.capability.noPermission.resetTransformTooltip', { - defaultMessage: 'You do not have permission to reset transforms.', - }); - break; - - case 'noTransformNodes': - message = i18n.translate('xpack.transform.capability.noPermission.noTransformNodesTooltip', { - defaultMessage: 'There are no transform nodes available.', - }); - break; - } - - return i18n.translate('xpack.transform.capability.pleaseContactAdministratorTooltip', { - defaultMessage: '{message} Please contact your administrator.', - values: { - message, - }, - }); -} diff --git a/x-pack/plugins/transform/common/types/capabilities.ts b/x-pack/plugins/transform/common/types/capabilities.ts new file mode 100644 index 0000000000000..ae5e241e3b0fe --- /dev/null +++ b/x-pack/plugins/transform/common/types/capabilities.ts @@ -0,0 +1,48 @@ +/* + * 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 { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +export const getInitialTransformCapabilities = (initialSetting = false) => ({ + canCreateTransform: initialSetting, + canCreateTransformAlerts: initialSetting, + canDeleteIndex: initialSetting, + canDeleteTransform: initialSetting, + canGetTransform: initialSetting, + canPreviewTransform: initialSetting, + canReauthorizeTransform: initialSetting, + canResetTransform: initialSetting, + canScheduleNowTransform: initialSetting, + canStartStopTransform: initialSetting, + canUseTransformAlerts: initialSetting, +}); + +export const isTransformCapabilities = (arg: unknown): arg is TransformCapabilities => { + return ( + isPopulatedObject(arg, Object.keys(getInitialTransformCapabilities())) && + Object.values(arg).every((d) => typeof d === 'boolean') + ); +}; + +export type TransformCapabilities = ReturnType; +export type TransformCapability = keyof TransformCapabilities; + +export interface PrivilegesAndCapabilities { + privileges: Privileges; + capabilities: TransformCapabilities; +} + +export type Privilege = [string, string]; + +export interface MissingPrivileges { + [key: string]: string[] | undefined; +} + +export interface Privileges { + hasAllPrivileges: boolean; + missingPrivileges: MissingPrivileges; +} diff --git a/x-pack/plugins/transform/common/types/privileges.ts b/x-pack/plugins/transform/common/types/privileges.ts deleted file mode 100644 index 702b62210d062..0000000000000 --- a/x-pack/plugins/transform/common/types/privileges.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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. - */ - -export interface MissingPrivileges { - [key: string]: string[] | undefined; -} - -export interface Privileges { - hasAllPrivileges: boolean; - missingPrivileges: MissingPrivileges; -} diff --git a/x-pack/plugins/transform/common/utils/create_capability_failure_message.ts b/x-pack/plugins/transform/common/utils/create_capability_failure_message.ts new file mode 100644 index 0000000000000..7f0f660bd2514 --- /dev/null +++ b/x-pack/plugins/transform/common/utils/create_capability_failure_message.ts @@ -0,0 +1,85 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import type { TransformCapabilities } from '../types/capabilities'; + +// create the text for button's tooltips if the user +// doesn't have the permission to press that button +export function createCapabilityFailureMessage( + capability: keyof TransformCapabilities | 'noTransformNodes' +) { + let message = ''; + + switch (capability) { + case 'canCreateTransform': + message = i18n.translate('xpack.transform.capability.noPermission.createTransformTooltip', { + defaultMessage: 'You do not have permission to create transforms.', + }); + break; + case 'canCreateTransformAlerts': + message = i18n.translate( + 'xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip', + { + defaultMessage: 'You do not have permission to create transform alert rules.', + } + ); + break; + case 'canScheduleNowTransform': + message = i18n.translate( + 'xpack.transform.capability.noPermission.scheduleNowTransformTooltip', + { + defaultMessage: + 'You do not have permission to schedule transforms to process data instantly.', + } + ); + break; + case 'canStartStopTransform': + message = i18n.translate( + 'xpack.transform.capability.noPermission.startOrStopTransformTooltip', + { + defaultMessage: 'You do not have permission to start or stop transforms.', + } + ); + break; + + case 'canReauthorizeTransform': + message = i18n.translate( + 'xpack.transform.capability.noPermission.reauthorizeTransformTooltip', + { + defaultMessage: 'You do not have permission to reauthorize transforms.', + } + ); + break; + + case 'canDeleteTransform': + message = i18n.translate('xpack.transform.capability.noPermission.deleteTransformTooltip', { + defaultMessage: 'You do not have permission to delete transforms.', + }); + break; + + case 'canResetTransform': + message = i18n.translate('xpack.transform.capability.noPermission.resetTransformTooltip', { + defaultMessage: 'You do not have permission to reset transforms.', + }); + break; + + case 'noTransformNodes': + message = i18n.translate('xpack.transform.capability.noPermission.noTransformNodesTooltip', { + defaultMessage: 'There are no transform nodes available.', + }); + break; + } + + return i18n.translate('xpack.transform.capability.pleaseContactAdministratorTooltip', { + defaultMessage: '{message} Please contact your administrator.', + values: { + message, + }, + }); +} diff --git a/x-pack/plugins/transform/public/app/lib/authorization/index.ts b/x-pack/plugins/transform/common/utils/to_array.ts similarity index 73% rename from x-pack/plugins/transform/public/app/lib/authorization/index.ts rename to x-pack/plugins/transform/common/utils/to_array.ts index e78d12e73a8ea..f41ceea9fc180 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/index.ts +++ b/x-pack/plugins/transform/common/utils/to_array.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './components'; +export const toArray = (value: T | T[]): T[] => (Array.isArray(value) ? value : [value]); diff --git a/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx b/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx index 56bd9a2db877e..49b8ee1a684fb 100644 --- a/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx +++ b/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_health_rule_trigger.tsx @@ -6,16 +6,15 @@ */ import { EuiForm, EuiSpacer } from '@elastic/eui'; -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { FC, useCallback, useEffect, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import type { TransformHealthRuleParams } from '../../../common/types/alerting'; import { TestsSelectionControl } from './tests_selection_control'; import { TransformSelectorControl } from './transform_selector_control'; -import { useApi } from '../../app/hooks'; +import { useGetTransforms } from '../../app/hooks'; import { useToastNotifications } from '../../app/app_dependencies'; -import { GetTransformsResponseSchema } from '../../../common/api_schemas/transforms'; import { ALL_TRANSFORMS_SELECTION } from '../../../common/constants'; export type TransformHealthRuleTriggerProps = @@ -29,9 +28,12 @@ const TransformHealthRuleTrigger: FC = ({ const formErrors = Object.values(errors).flat(); const isFormInvalid = formErrors.length > 0; - const api = useApi(); const toast = useToastNotifications(); - const [transformOptions, setTransformOptions] = useState([]); + const { error, data } = useGetTransforms(); + const transformOptions = useMemo( + () => data?.transforms.filter((v) => v.config.sync).map((v) => v.id) ?? [], + [data] + ); const onAlertParamChange = useCallback( (param: T) => @@ -41,34 +43,18 @@ const TransformHealthRuleTrigger: FC = ({ [setRuleParams] ); - useEffect( - function fetchTransforms() { - let unmounted = false; - api - .getTransforms() - .then((r) => { - if (!unmounted) { - setTransformOptions( - (r as GetTransformsResponseSchema).transforms.filter((v) => v.sync).map((v) => v.id) - ); + useEffect(() => { + if (error !== null) { + toast.addError(error, { + title: i18n.translate( + 'xpack.transform.alertingRuleTypes.transformHealth.fetchErrorMessage', + { + defaultMessage: 'Unable to fetch transforms', } - }) - .catch((e) => { - toast.addError(e, { - title: i18n.translate( - 'xpack.transform.alertingRuleTypes.transformHealth.fetchErrorMessage', - { - defaultMessage: 'Unable to fetch transforms', - } - ), - }); - }); - return () => { - unmounted = true; - }; - }, - [api, toast] - ); + ), + }); + } + }, [error, toast]); const excludeTransformOptions = useMemo(() => { if (ruleParams.includeTransforms?.some((v) => v === ALL_TRANSFORMS_SELECTION)) { diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index ba4a43bfa0876..505009784d26e 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useContext, FC } from 'react'; +import React, { type FC } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -13,68 +13,50 @@ import { EuiErrorBoundary } from '@elastic/eui'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { ScopedHistory } from '@kbn/core/public'; -import { FormattedMessage } from '@kbn/i18n-react'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import { addInternalBasePath } from '../../common/constants'; - -import { SectionError } from './components'; import { SECTION_SLUG } from './common/constants'; -import { AuthorizationContext, AuthorizationProvider } from './lib/authorization'; import { AppDependencies } from './app_dependencies'; import { CloneTransformSection } from './sections/clone_transform'; import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; -export const App: FC<{ history: ScopedHistory }> = ({ history }) => { - const { apiError } = useContext(AuthorizationContext); - if (apiError !== null) { - return ( - - } - error={apiError} +export const App: FC<{ history: ScopedHistory }> = ({ history }) => ( + + + - ); - } - - return ( - - - - - - - - ); -}; + + + + +); export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => { const I18nContext = appDependencies.i18n.Context; - const queryClient = new QueryClient(); + + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: Infinity, + retry: false, + }, + }, + }); render( - - - - - + + + diff --git a/x-pack/plugins/transform/public/app/common/index.ts b/x-pack/plugins/transform/public/app/common/index.ts index c7656974ec569..2bea1421277ef 100644 --- a/x-pack/plugins/transform/public/app/common/index.ts +++ b/x-pack/plugins/transform/public/app/common/index.ts @@ -19,12 +19,7 @@ export { toggleSelectedField, } from './fields'; export type { DropDownLabel, DropDownOption, Label } from './dropdown'; -export { - isTransformIdValid, - refreshTransformList$, - useRefreshTransformList, - REFRESH_TRANSFORM_LIST_STATE, -} from './transform'; +export { isTransformIdValid } from './transform'; export type { TransformListAction, TransformListRow } from './transform_list'; export { TRANSFORM_LIST_COLUMN } from './transform_list'; export { getTransformProgress, isCompletedBatchTransform } from './transform_stats'; diff --git a/x-pack/plugins/transform/public/app/common/navigation.tsx b/x-pack/plugins/transform/public/app/common/navigation.tsx index 601e701c7f16d..ef6696d3e757a 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { Redirect } from 'react-router-dom'; import { SECTION_SLUG } from './constants'; diff --git a/x-pack/plugins/transform/public/app/common/transform.ts b/x-pack/plugins/transform/public/app/common/transform.ts index 35ead5691a866..b689ee8096983 100644 --- a/x-pack/plugins/transform/public/app/common/transform.ts +++ b/x-pack/plugins/transform/public/app/common/transform.ts @@ -5,11 +5,8 @@ * 2.0. */ -import { useEffect } from 'react'; -import { BehaviorSubject } from 'rxjs'; -import { filter, distinctUntilChanged } from 'rxjs/operators'; -import { Subscription } from 'rxjs'; import { cloneDeep } from 'lodash'; + import type { TransformConfigUnion, TransformId } from '../../../common/types/transform'; // Via https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/utils/TransformStrings.java#L24 @@ -23,64 +20,6 @@ export function isTransformIdValid(transformId: TransformId) { export const TRANSFORM_ERROR_TYPE = { DANGLING_TASK: 'dangling_task', } as const; -export enum REFRESH_TRANSFORM_LIST_STATE { - ERROR = 'error', - IDLE = 'idle', - LOADING = 'loading', - REFRESH = 'refresh', -} -export const refreshTransformList$ = new BehaviorSubject( - REFRESH_TRANSFORM_LIST_STATE.IDLE -); - -export const useRefreshTransformList = ( - callback: { - isLoading?(d: boolean): void; - onRefresh?(): void; - } = {} -) => { - useEffect(() => { - const distinct$ = refreshTransformList$.pipe(distinctUntilChanged()); - - const subscriptions: Subscription[] = []; - - if (typeof callback.onRefresh === 'function') { - // initial call to refresh - callback.onRefresh(); - - subscriptions.push( - distinct$ - .pipe(filter((state) => state === REFRESH_TRANSFORM_LIST_STATE.REFRESH)) - .subscribe(() => typeof callback.onRefresh === 'function' && callback.onRefresh()) - ); - } - - if (typeof callback.isLoading === 'function') { - subscriptions.push( - distinct$.subscribe( - (state) => - typeof callback.isLoading === 'function' && - callback.isLoading(state === REFRESH_TRANSFORM_LIST_STATE.LOADING) - ) - ); - } - - return () => { - subscriptions.map((sub) => sub.unsubscribe()); - }; - // The effect should only be called once. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return { - refresh: () => { - // A refresh is followed immediately by setting the state to loading - // to trigger data fetching and loading indicators in one go. - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.LOADING); - }, - }; -}; export const overrideTransformForCloning = (originalConfig: TransformConfigUnion) => { // 'Managed' means job is preconfigured and deployed by other solutions diff --git a/x-pack/plugins/transform/public/app/components/capabilities_wrapper.tsx b/x-pack/plugins/transform/public/app/components/capabilities_wrapper.tsx new file mode 100644 index 0000000000000..ce850aa2ffc6c --- /dev/null +++ b/x-pack/plugins/transform/public/app/components/capabilities_wrapper.tsx @@ -0,0 +1,59 @@ +/* + * 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 React, { type FC } from 'react'; + +import { EuiFlexItem, EuiFlexGroup, EuiPageTemplate, EuiEmptyPrompt } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { TransformCapability } from '../../../common/types/capabilities'; +import { toArray } from '../../../common/utils/to_array'; + +import { useTransformCapabilities } from '../hooks'; + +interface Props { + title: React.ReactNode; + message: React.ReactNode | string; +} + +export const NotAuthorizedSection = ({ title, message }: Props) => ( + {title}} body={

{message}

} /> +); + +const MissingCapabilities: FC = () => ( + + + + + } + message={ + + } + /> + + + +); + +export const CapabilitiesWrapper: FC<{ + requiredCapabilities: TransformCapability | TransformCapability[]; +}> = ({ children, requiredCapabilities }) => { + const capabilities = useTransformCapabilities(); + + const hasCapabilities = toArray(requiredCapabilities).every((c) => capabilities[c]); + + return hasCapabilities ? <>{children} : ; +}; diff --git a/x-pack/plugins/transform/public/app/components/index.ts b/x-pack/plugins/transform/public/app/components/index.ts index 7c3cb454aecb3..2edcf498e5832 100644 --- a/x-pack/plugins/transform/public/app/components/index.ts +++ b/x-pack/plugins/transform/public/app/components/index.ts @@ -5,6 +5,4 @@ * 2.0. */ -export { SectionError } from './section_error'; -export { SectionLoading } from './section_loading'; export { ToastNotificationText } from './toast_notification_text'; diff --git a/x-pack/plugins/transform/public/app/components/job_icon.tsx b/x-pack/plugins/transform/public/app/components/job_icon.tsx index 09db5a1c52377..12a5687156611 100644 --- a/x-pack/plugins/transform/public/app/components/job_icon.tsx +++ b/x-pack/plugins/transform/public/app/components/job_icon.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { AuditMessageBase } from '../../../common/types/messages'; diff --git a/x-pack/plugins/transform/public/app/components/section_error.tsx b/x-pack/plugins/transform/public/app/components/section_error.tsx deleted file mode 100644 index 2a6ddd3174de7..0000000000000 --- a/x-pack/plugins/transform/public/app/components/section_error.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 React from 'react'; -import { EuiPageTemplate } from '@elastic/eui'; - -interface Props { - title: React.ReactNode; - error: Error | null; - actions?: JSX.Element; -} - -export const SectionError: React.FunctionComponent = ({ - title, - error, - actions, - ...rest -}) => { - const errorMessage = error?.message ?? JSON.stringify(error, null, 2); - - return ( - {title}} - body={ -

-

{errorMessage}
- {actions ? actions : null} -

- } - /> - ); -}; diff --git a/x-pack/plugins/transform/public/app/components/section_loading.tsx b/x-pack/plugins/transform/public/app/components/section_loading.tsx deleted file mode 100644 index c1548ad960bb0..0000000000000 --- a/x-pack/plugins/transform/public/app/components/section_loading.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 React from 'react'; - -import { - EuiEmptyPrompt, - EuiLoadingSpinner, - EuiText, - EuiFlexGroup, - EuiFlexItem, - EuiTextColor, -} from '@elastic/eui'; - -interface Props { - inline?: boolean; - children: React.ReactNode; - [key: string]: any; -} - -export const SectionLoading: React.FunctionComponent = ({ inline, children, ...rest }) => { - if (inline) { - return ( - - - - - - - {children} - - - - ); - } - - return ( - } - body={{children}} - data-test-subj="sectionLoading" - /> - ); -}; diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx index 6409a8fcf3b45..8b2f154e8124e 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiButtonEmpty, @@ -19,41 +19,45 @@ import { import { i18n } from '@kbn/i18n'; -import { CoreStart } from '@kbn/core/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { useAppDependencies } from '../app_dependencies'; const MAX_SIMPLE_MESSAGE_LENGTH = 140; -// Because of the use of `toMountPoint`, `useKibanaContext` doesn't work via `useAppDependencies`. -// That's why we need to pass in `overlays` as a prop cannot get it via context. interface ToastNotificationTextProps { - overlays: CoreStart['overlays']; - theme: CoreStart['theme']; text: any; previewTextLength?: number; + inline?: boolean; + forceModal?: boolean; } export const ToastNotificationText: FC = ({ - overlays, text, - theme, previewTextLength, + inline = false, + forceModal = false, }) => { - if (typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) { + const { overlays, theme, i18n: i18nStart } = useAppDependencies(); + + if (!forceModal && typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) { return text; } if ( + !forceModal && typeof text === 'object' && + text !== null && typeof text.message === 'string' && text.message.length <= MAX_SIMPLE_MESSAGE_LENGTH ) { return text.message; } - const unformattedText = text.message ? text.message : text; - const formattedText = typeof unformattedText === 'object' ? JSON.stringify(text, null, 2) : text; + const unformattedText = + typeof text === 'object' && text !== null && text.message ? text.message : text; + const formattedText = + typeof unformattedText === 'object' ? JSON.stringify(text, null, 2) : unformattedText; const textLength = previewTextLength ?? 140; const previewText = `${formattedText.substring(0, textLength)}${ formattedText.length > textLength ? ' ...' : '' @@ -83,15 +87,19 @@ export const ToastNotificationText: FC = ({ , - { theme$: theme.theme$ } + { theme, i18n: i18nStart } ) ); }; return ( <> -
{previewText}
- + {!inline &&
{previewText}
} + {i18n.translate('xpack.transform.toastText.openModalButtonText', { defaultMessage: 'View details', })} diff --git a/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts b/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts deleted file mode 100644 index 2e0407b0ac974..0000000000000 --- a/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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 type { IHttpFetchError } from '@kbn/core-http-browser'; - -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils'; - -import type { TransformId } from '../../../../common/types/transform'; -import type { FieldHistogramsResponseSchema } from '../../../../common/api_schemas/field_histograms'; -import type { GetTransformsAuditMessagesResponseSchema } from '../../../../common/api_schemas/audit_messages'; -import type { - DeleteTransformsRequestSchema, - DeleteTransformsResponseSchema, -} from '../../../../common/api_schemas/delete_transforms'; -import type { - StartTransformsRequestSchema, - StartTransformsResponseSchema, -} from '../../../../common/api_schemas/start_transforms'; -import type { - StopTransformsRequestSchema, - StopTransformsResponseSchema, -} from '../../../../common/api_schemas/stop_transforms'; -import type { - GetTransformsResponseSchema, - PostTransformsPreviewRequestSchema, - PostTransformsPreviewResponseSchema, - PutTransformsRequestSchema, - PutTransformsResponseSchema, -} from '../../../../common/api_schemas/transforms'; -import type { GetTransformsStatsResponseSchema } from '../../../../common/api_schemas/transforms_stats'; -import type { - PostTransformsUpdateRequestSchema, - PostTransformsUpdateResponseSchema, -} from '../../../../common/api_schemas/update_transforms'; - -import type { EsIndex } from '../../../../common/types/es_index'; - -import type { SavedSearchQuery } from '../use_search_items'; - -export interface FieldHistogramRequestConfig { - fieldName: string; - type?: KBN_FIELD_TYPES; -} - -const apiFactory = () => ({ - async getTransform( - transformId: TransformId - ): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async getTransforms(): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async getTransformStats( - transformId: TransformId - ): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async getTransformsStats(): Promise { - return Promise.resolve({ count: 0, transforms: [] }); - }, - async createTransform( - transformId: TransformId, - transformConfig: PutTransformsRequestSchema - ): Promise { - return Promise.resolve({ transformsCreated: [], errors: [] }); - }, - async updateTransform( - transformId: TransformId, - transformConfig: PostTransformsUpdateRequestSchema - ): Promise { - return Promise.resolve({ - id: 'the-test-id', - source: { index: ['the-index-name'], query: { match_all: {} } }, - dest: { index: 'user-the-destination-index-name' }, - frequency: '10m', - pivot: { - group_by: { the_group: { terms: { field: 'the-group-by-field' } } }, - aggregations: { the_agg: { value_count: { field: 'the-agg-field' } } }, - }, - description: 'the-description', - settings: { docs_per_second: null }, - version: '8.0.0', - create_time: 1598860879097, - }); - }, - async deleteTransforms( - reqBody: DeleteTransformsRequestSchema - ): Promise { - return Promise.resolve({}); - }, - async getTransformsPreview( - obj: PostTransformsPreviewRequestSchema - ): Promise { - return Promise.resolve({ - generated_dest_index: { - mappings: { - _meta: { - _transform: { - transform: 'the-transform', - version: { create: 'the-version' }, - creation_date_in_millis: 0, - }, - created_by: 'mock', - }, - properties: {}, - }, - settings: { index: { number_of_shards: '1', auto_expand_replicas: '0-1' } }, - aliases: {}, - }, - preview: [], - }); - }, - async startTransforms( - reqBody: StartTransformsRequestSchema - ): Promise { - return Promise.resolve({}); - }, - async stopTransforms( - transformsInfo: StopTransformsRequestSchema - ): Promise { - return Promise.resolve({}); - }, - async getTransformAuditMessages( - transformId: TransformId - ): Promise { - return Promise.resolve({ messages: [], total: 0 }); - }, - - async getEsIndices(): Promise { - return Promise.resolve([]); - }, - async getHistogramsForFields( - dataViewTitle: string, - fields: FieldHistogramRequestConfig[], - query: string | SavedSearchQuery, - samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE - ): Promise { - return Promise.resolve([]); - }, -}); - -export const useApi = () => { - return apiFactory(); -}; diff --git a/x-pack/plugins/transform/public/app/hooks/index.ts b/x-pack/plugins/transform/public/app/hooks/index.ts index f6a4c72b39a44..749706f97cd70 100644 --- a/x-pack/plugins/transform/public/app/hooks/index.ts +++ b/x-pack/plugins/transform/public/app/hooks/index.ts @@ -5,10 +5,23 @@ * 2.0. */ -export { useApi } from './use_api'; +export { useCreateTransform } from './use_create_transform'; +export { useDocumentationLinks } from './use_documentation_links'; +export { useGetDataViewTitles } from './use_get_data_view_titles'; +export { useGetEsIndices } from './use_get_es_indices'; +export { useGetEsIngestPipelines } from './use_get_es_ingest_pipelines'; +export { useGetTransformAuditMessages } from './use_get_transform_audit_messages'; +export { useGetTransform } from './use_get_transform'; +export { useGetTransformNodes } from './use_get_transform_nodes'; export { useGetTransforms } from './use_get_transforms'; +export { useGetTransformsPreview } from './use_get_transforms_preview'; +export { useGetTransformStats } from './use_get_transform_stats'; export { useDeleteTransforms, useDeleteIndexAndTargetIndex } from './use_delete_transform'; +export { useRefreshTransformList } from './use_refresh_transform_list'; export { useResetTransforms } from './use_reset_transform'; +export { useSearchItems } from './use_search_items'; export { useScheduleNowTransforms } from './use_schedule_now_transform'; export { useStartTransforms } from './use_start_transform'; export { useStopTransforms } from './use_stop_transform'; +export { useTransformCapabilities } from './use_transform_capabilities'; +export { useUpdateTransform } from './use_update_transform'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts deleted file mode 100644 index 066977fb841df..0000000000000 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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 { useMemo } from 'react'; - -import type { IHttpFetchError } from '@kbn/core-http-browser'; - -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils'; - -import { - ReauthorizeTransformsRequestSchema, - ReauthorizeTransformsResponseSchema, -} from '../../../common/api_schemas/reauthorize_transforms'; -import type { GetTransformsAuditMessagesResponseSchema } from '../../../common/api_schemas/audit_messages'; -import type { - DeleteTransformsRequestSchema, - DeleteTransformsResponseSchema, -} from '../../../common/api_schemas/delete_transforms'; -import type { - FieldHistogramsRequestSchema, - FieldHistogramsResponseSchema, -} from '../../../common/api_schemas/field_histograms'; -import type { - ResetTransformsRequestSchema, - ResetTransformsResponseSchema, -} from '../../../common/api_schemas/reset_transforms'; -import type { - StartTransformsRequestSchema, - StartTransformsResponseSchema, -} from '../../../common/api_schemas/start_transforms'; -import type { - StopTransformsRequestSchema, - StopTransformsResponseSchema, -} from '../../../common/api_schemas/stop_transforms'; -import type { - ScheduleNowTransformsRequestSchema, - ScheduleNowTransformsResponseSchema, -} from '../../../common/api_schemas/schedule_now_transforms'; -import type { - GetTransformNodesResponseSchema, - GetTransformsResponseSchema, - PostTransformsPreviewRequestSchema, - PostTransformsPreviewResponseSchema, - PutTransformsRequestSchema, - PutTransformsResponseSchema, -} from '../../../common/api_schemas/transforms'; -import type { - PostTransformsUpdateRequestSchema, - PostTransformsUpdateResponseSchema, -} from '../../../common/api_schemas/update_transforms'; -import type { GetTransformsStatsResponseSchema } from '../../../common/api_schemas/transforms_stats'; -import type { TransformId } from '../../../common/types/transform'; -import { addInternalBasePath } from '../../../common/constants'; -import type { EsIndex } from '../../../common/types/es_index'; -import type { EsIngestPipeline } from '../../../common/types/es_ingest_pipeline'; - -import { useAppDependencies } from '../app_dependencies'; - -import type { SavedSearchQuery } from './use_search_items'; - -export interface FieldHistogramRequestConfig { - fieldName: string; - type?: KBN_FIELD_TYPES; -} - -interface FetchOptions { - asSystemRequest?: boolean; -} - -export const useApi = () => { - const { http } = useAppDependencies(); - - return useMemo( - () => ({ - async getTransformNodes(): Promise { - try { - return await http.get(addInternalBasePath(`transforms/_nodes`), { version: '1' }); - } catch (e) { - return e; - } - }, - async getTransform( - transformId: TransformId - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms/${transformId}`), { - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransforms( - fetchOptions: FetchOptions = {} - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms`), { - ...fetchOptions, - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformStats( - transformId: TransformId - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms/${transformId}/_stats`), { - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformsStats( - fetchOptions: FetchOptions = {} - ): Promise { - try { - return await http.get(addInternalBasePath(`transforms/_stats`), { - ...fetchOptions, - version: '1', - }); - } catch (e) { - return e; - } - }, - async createTransform( - transformId: TransformId, - transformConfig: PutTransformsRequestSchema - ): Promise { - try { - return await http.put(addInternalBasePath(`transforms/${transformId}`), { - body: JSON.stringify(transformConfig), - version: '1', - }); - } catch (e) { - return e; - } - }, - async updateTransform( - transformId: TransformId, - transformConfig: PostTransformsUpdateRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`transforms/${transformId}/_update`), { - body: JSON.stringify(transformConfig), - version: '1', - }); - } catch (e) { - return e; - } - }, - async deleteTransforms( - reqBody: DeleteTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`delete_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformsPreview( - obj: PostTransformsPreviewRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`transforms/_preview`), { - body: JSON.stringify(obj), - version: '1', - }); - } catch (e) { - return e; - } - }, - async reauthorizeTransforms( - reqBody: ReauthorizeTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`reauthorize_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - - async resetTransforms( - reqBody: ResetTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`reset_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - async startTransforms( - reqBody: StartTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`start_transforms`), { - body: JSON.stringify(reqBody), - version: '1', - }); - } catch (e) { - return e; - } - }, - async stopTransforms( - transformsInfo: StopTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`stop_transforms`), { - body: JSON.stringify(transformsInfo), - version: '1', - }); - } catch (e) { - return e; - } - }, - async scheduleNowTransforms( - transformsInfo: ScheduleNowTransformsRequestSchema - ): Promise { - try { - return await http.post(addInternalBasePath(`schedule_now_transforms`), { - body: JSON.stringify(transformsInfo), - version: '1', - }); - } catch (e) { - return e; - } - }, - async getTransformAuditMessages( - transformId: TransformId, - sortField: string, - sortDirection: 'asc' | 'desc' - ): Promise< - { messages: GetTransformsAuditMessagesResponseSchema; total: number } | IHttpFetchError - > { - try { - return await http.get(addInternalBasePath(`transforms/${transformId}/messages`), { - query: { - sortField, - sortDirection, - }, - version: '1', - }); - } catch (e) { - return e; - } - }, - async getEsIndices(): Promise { - try { - return await http.get(`/api/index_management/indices`, { version: '1' }); - } catch (e) { - return e; - } - }, - async getEsIngestPipelines(): Promise { - try { - return await http.get('/api/ingest_pipelines', { version: '1' }); - } catch (e) { - return e; - } - }, - async getHistogramsForFields( - dataViewTitle: string, - fields: FieldHistogramRequestConfig[], - query: string | SavedSearchQuery, - runtimeMappings?: FieldHistogramsRequestSchema['runtimeMappings'], - samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE - ): Promise { - try { - return await http.post(addInternalBasePath(`field_histograms/${dataViewTitle}`), { - body: JSON.stringify({ - query, - fields, - samplerShardSize, - ...(runtimeMappings !== undefined ? { runtimeMappings } : {}), - }), - version: '1', - }); - } catch (e) { - return e; - } - }, - }), - [http] - ); -}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx new file mode 100644 index 0000000000000..272e815c42eb4 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx @@ -0,0 +1,71 @@ +/* + * 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 React from 'react'; +import { useMutation } from '@tanstack/react-query'; + +import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/react-kibana-mount'; + +import type { + PutTransformsRequestSchema, + PutTransformsResponseSchema, +} from '../../../common/api_schemas/transforms'; +import { addInternalBasePath } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; +import { getErrorMessage } from '../../../common/utils/errors'; + +import { useAppDependencies, useToastNotifications } from '../app_dependencies'; +import { ToastNotificationText } from '../components'; + +import { useRefreshTransformList } from './use_refresh_transform_list'; + +interface CreateTransformArgs { + transformId: TransformId; + transformConfig: PutTransformsRequestSchema; +} + +export const useCreateTransform = () => { + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); + const toastNotifications = useToastNotifications(); + + function errorToast(error: unknown, { transformId }: CreateTransformArgs) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepCreateForm.createTransformErrorMessage', { + defaultMessage: 'An error occurred creating the transform {transformId}:', + values: { transformId }, + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + + const mutation = useMutation({ + mutationFn: ({ transformId, transformConfig }: CreateTransformArgs) => { + return http.put( + addInternalBasePath(`transforms/${transformId}`), + { + body: JSON.stringify(transformConfig), + version: '1', + } + ); + }, + onError: errorToast, + onSuccess: (resp, options) => { + if (resp.errors.length > 0) { + errorToast(resp.errors.length === 1 ? resp.errors[0] : resp.errors, options); + } + + refreshTransformList(); + }, + }); + + return mutation.mutate; +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_data_search.ts b/x-pack/plugins/transform/public/app/hooks/use_data_search.ts index af4bb440f9e24..f691cb7a0d8b6 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_data_search.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_data_search.ts @@ -5,37 +5,37 @@ * 2.0. */ -import { useCallback } from 'react'; +import { useQuery } from '@tanstack/react-query'; import { lastValueFrom } from 'rxjs'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import type { IKibanaSearchRequest } from '@kbn/data-plugin/common'; +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + import { useAppDependencies } from '../app_dependencies'; -export const useDataSearch = () => { +export const useDataSearch = ( + esSearchRequestParams: IKibanaSearchRequest['params'], + enabled?: boolean +) => { const { data } = useAppDependencies(); - return useCallback( - async (esSearchRequestParams: IKibanaSearchRequest['params'], abortSignal?: AbortSignal) => { - try { - const { rawResponse: resp } = await lastValueFrom( - data.search.search( - { - params: esSearchRequestParams, - }, - { abortSignal } - ) - ); - - return resp; - } catch (error) { - if (error.name === 'AbortError') { - // ignore abort errors - } else { - return error; - } - } + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.DATA_SEARCH, esSearchRequestParams], + async ({ signal }) => { + const { rawResponse: resp } = await lastValueFrom( + data.search.search( + { + params: esSearchRequestParams, + }, + { abortSignal: signal } + ) + ); + + return resp; }, - [data] + { enabled } ); }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts b/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts new file mode 100644 index 0000000000000..d74fa9c909a5d --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_data_view_exists.ts @@ -0,0 +1,38 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { ErrorType } from '@kbn/ml-error-utils'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; +import type { TransformListRow } from '../common'; + +export const useDataViewExists = (items: TransformListRow[]) => { + const { + data: { dataViews: dataViewsContract }, + } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.DATA_VIEW_EXISTS, items], + async () => { + if (items.length !== 1) { + return false; + } + const config = items[0].config; + const indexName = Array.isArray(config.dest.index) ? config.dest.index[0] : config.dest.index; + + if (indexName === undefined) { + return false; + } + + return (await dataViewsContract.find(indexName)).some(({ title }) => title === indexName); + } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index 25318fc9e2903..a7ed779c47cc7 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -6,34 +6,40 @@ */ import React, { useCallback, useEffect, useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; + import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { extractErrorMessage } from '@kbn/ml-error-utils'; + +import { addInternalBasePath } from '../../../common/constants'; import type { - DeleteTransformStatus, DeleteTransformsRequestSchema, + DeleteTransformsResponseSchema, } from '../../../common/api_schemas/delete_transforms'; -import { isDeleteTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; import { getErrorMessage } from '../../../common/utils/errors'; + import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$, TransformListRow } from '../common'; +import { type TransformListRow } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; -import { indexService } from '../services/es_index_service'; + +import { useTransformCapabilities } from './use_transform_capabilities'; +import { useDataViewExists } from './use_data_view_exists'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { const { - http, - data: { dataViews: dataViewsContract }, application: { capabilities }, } = useAppDependencies(); const toastNotifications = useToastNotifications(); + const { canDeleteIndex: userCanDeleteIndex } = useTransformCapabilities(); + + const userCanDeleteDataView = + capabilities.savedObjectsManagement?.delete === true || + capabilities.indexPatterns?.save === true; const [deleteDestIndex, setDeleteDestIndex] = useState(true); - const [deleteDataView, setDeleteDataView] = useState(true); - const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); - const [dataViewExists, setDataViewExists] = useState(false); - const [userCanDeleteDataView, setUserCanDeleteDataView] = useState(false); + const [deleteDataView, setDeleteDataView] = useState(userCanDeleteDataView); const toggleDeleteIndex = useCallback( () => setDeleteDestIndex(!deleteDestIndex), @@ -43,67 +49,31 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { () => setDeleteDataView(!deleteDataView), [deleteDataView] ); - const checkDataViewExists = useCallback( - async (indexName: string) => { - try { - const dvExists = await indexService.dataViewExists(dataViewsContract, indexName); - setDataViewExists(dvExists); - } catch (e) { - const error = extractErrorMessage(e); - toastNotifications.addDanger( - i18n.translate( - 'xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage', - { - defaultMessage: 'An error occurred checking if data view {dataView} exists: {error}', - values: { dataView: indexName, error }, - } - ) - ); - } - }, - [dataViewsContract, toastNotifications] - ); + const { error: dataViewExistsError, data: dataViewExists = items.length !== 1 } = + useDataViewExists(items); + + useEffect(() => { + if (dataViewExistsError !== null && items.length === 1) { + const config = items[0].config; + const indexName = Array.isArray(config.dest.index) ? config.dest.index[0] : config.dest.index; - const checkUserIndexPermission = useCallback(async () => { - try { - const userCanDelete = await indexService.canDeleteIndex(http); - if (userCanDelete) { - setUserCanDeleteIndex(true); - } - const canDeleteDataView = - capabilities.savedObjectsManagement.delete === true || - capabilities.indexPatterns.save === true; - setUserCanDeleteDataView(canDeleteDataView); - if (canDeleteDataView === false) { - setDeleteDataView(false); - } - } catch (e) { toastNotifications.addDanger( i18n.translate( - 'xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage', + 'xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage', { - defaultMessage: 'An error occurred checking if user can delete destination index', + defaultMessage: 'An error occurred checking if data view {dataView} exists: {error}', + values: { + dataView: indexName, + error: extractErrorMessage(dataViewExistsError), + }, } ) ); } - }, [http, toastNotifications, capabilities]); - - useEffect(() => { - checkUserIndexPermission(); - - // if user only deleting one transform - if (items.length === 1) { - const config = items[0].config; - const destinationIndex = Array.isArray(config.dest.index) - ? config.dest.index[0] - : config.dest.index; - checkDataViewExists(destinationIndex); - } else { - setDataViewExists(true); - } - }, [checkDataViewExists, checkUserIndexPermission, items]); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataViewExistsError]); return { userCanDeleteIndex, @@ -116,183 +86,87 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { }; }; -type SuccessCountField = keyof Omit; - export const useDeleteTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - return async (reqBody: DeleteTransformsRequestSchema) => { - const results = await api.deleteTransforms(reqBody); - - if (!isDeleteTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: DeleteTransformsRequestSchema) => + http.post(addInternalBasePath('delete_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate('xpack.transform.transformList.deleteTransformGenericErrorMessage', { defaultMessage: 'An error occurred calling the API endpoint to delete transforms.', }), text: toMountPoint( - , - { theme$: theme.theme$ } + , + { theme, i18n: i18nStart } ), - }); - return; - } - - const isBulk = Object.keys(results).length > 1; - const successCount: Record = { - transformDeleted: 0, - destIndexDeleted: 0, - destDataViewDeleted: 0, - }; - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const status = results[transformId]; - const destinationIndex = status.destinationIndex; - - // if we are only deleting one transform, show the success toast messages - if (!isBulk && status.transformDeleted) { - if (status.transformDeleted?.success) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.deleteTransformSuccessMessage', { - defaultMessage: 'Request to delete transform {transformId} acknowledged.', + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const status = results[transformId]; + const destinationIndex = status.destinationIndex; + + if (status.transformDeleted?.error) { + const error = status.transformDeleted.error.reason; + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.transformList.deleteTransformErrorMessage', { + defaultMessage: 'An error occurred deleting the transform {transformId}', values: { transformId }, - }) - ); + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - if (status.destIndexDeleted?.success) { - toastNotifications.addSuccess( - i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage', + + if (status.destIndexDeleted?.error) { + const error = status.destIndexDeleted.error.reason; + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage', { - defaultMessage: - 'Request to delete destination index {destinationIndex} acknowledged.', + defaultMessage: 'An error occurred deleting destination index {destinationIndex}', values: { destinationIndex }, } - ) - ); + ), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - if (status.destDataViewDeleted?.success) { - toastNotifications.addSuccess( - i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage', + + if (status.destDataViewDeleted?.error) { + const error = status.destDataViewDeleted.error.reason; + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage', { - defaultMessage: 'Request to delete data view {destinationIndex} acknowledged.', + defaultMessage: 'An error occurred deleting data view {destinationIndex}', values: { destinationIndex }, } - ) - ); + ), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - } else { - (Object.keys(successCount) as SuccessCountField[]).forEach((key) => { - if (status[key]?.success) { - successCount[key] = successCount[key] + 1; - } - }); - } - if (status.transformDeleted?.error) { - const error = status.transformDeleted.error.reason; - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.transformList.deleteTransformErrorMessage', { - defaultMessage: 'An error occurred deleting the transform {transformId}', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } - - if (status.destIndexDeleted?.error) { - const error = status.destIndexDeleted.error.reason; - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage', - { - defaultMessage: 'An error occurred deleting destination index {destinationIndex}', - values: { destinationIndex }, - } - ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } - - if (status.destDataViewDeleted?.error) { - const error = status.destDataViewDeleted.error.reason; - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage', - { - defaultMessage: 'An error occurred deleting data view {destinationIndex}', - values: { destinationIndex }, - } - ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); } } - } - // if we are deleting multiple transforms, combine the success messages - if (isBulk) { - if (successCount.transformDeleted > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkDeleteTransformSuccessMessage', { - defaultMessage: - 'Successfully deleted {count} {count, plural, one {transform} other {transforms}}.', - values: { count: successCount.transformDeleted }, - }) - ); - } - - if (successCount.destIndexDeleted > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage', { - defaultMessage: - 'Successfully deleted {count} destination {count, plural, one {index} other {indices}}.', - values: { count: successCount.destIndexDeleted }, - }) - ); - } - if (successCount.destDataViewDeleted > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage', { - defaultMessage: - 'Successfully deleted {count} destination data {count, plural, one {view} other {views}}.', - values: { count: successCount.destDataViewDeleted }, - }) - ); - } - } + refreshTransformList(); + }, + }); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_data_view_titles.ts b/x-pack/plugins/transform/public/app/hooks/use_get_data_view_titles.ts new file mode 100644 index 0000000000000..449ac30b9f513 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_data_view_titles.ts @@ -0,0 +1,23 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetDataViewTitles = () => { + const { data } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_DATA_VIEW_TITLES], + () => data.dataViews.getTitles() + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_es_indices.ts b/x-pack/plugins/transform/public/app/hooks/use_get_es_indices.ts new file mode 100644 index 0000000000000..7da50c155d1be --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_es_indices.ts @@ -0,0 +1,28 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { EsIndex } from '../../../common/types/es_index'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetEsIndices = () => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_ES_INDICES], + ({ signal }) => + http.get('/api/index_management/indices', { + version: '1', + signal, + }) + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_es_ingest_pipelines.ts b/x-pack/plugins/transform/public/app/hooks/use_get_es_ingest_pipelines.ts new file mode 100644 index 0000000000000..3f9784c64b652 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_es_ingest_pipelines.ts @@ -0,0 +1,28 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { EsIngestPipeline } from '../../../common/types/es_ingest_pipeline'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetEsIngestPipelines = () => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_ES_INGEST_PIPELINES], + ({ signal }) => + http.get('/api/ingest_pipelines', { + version: '1', + signal, + }) + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_histograms_for_fields.ts b/x-pack/plugins/transform/public/app/hooks/use_get_histograms_for_fields.ts new file mode 100644 index 0000000000000..63ce23dba6214 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_histograms_for_fields.ts @@ -0,0 +1,66 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { KBN_FIELD_TYPES } from '@kbn/field-types'; +import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils'; + +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { + FieldHistogramsRequestSchema, + FieldHistogramsResponseSchema, +} from '../../../common/api_schemas/field_histograms'; + +import { useAppDependencies } from '../app_dependencies'; + +import type { SavedSearchQuery } from './use_search_items'; + +export interface FieldHistogramRequestConfig { + fieldName: string; + type?: KBN_FIELD_TYPES; +} + +export const useGetHistogramsForFields = ( + dataViewTitle: string, + fields: FieldHistogramRequestConfig[], + query: string | SavedSearchQuery, + runtimeMappings?: FieldHistogramsRequestSchema['runtimeMappings'], + enabled?: boolean, + samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE +) => { + const { http } = useAppDependencies(); + + return useQuery( + [ + TRANSFORM_REACT_QUERY_KEYS.GET_HISTOGRAMS_FOR_FIELDS, + { + dataViewTitle, + fields, + query, + runtimeMappings, + samplerShardSize, + }, + ], + ({ signal }) => + http.post( + addInternalBasePath(`field_histograms/${dataViewTitle}`), + { + body: JSON.stringify({ + query, + fields, + samplerShardSize, + ...(runtimeMappings !== undefined ? { runtimeMappings } : {}), + }), + version: '1', + signal, + } + ), + { enabled } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_get_transform.tsx new file mode 100644 index 0000000000000..b9e3d977c71bd --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform.tsx @@ -0,0 +1,30 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformsResponseSchema } from '../../../common/api_schemas/transforms'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransform = (transformId: TransformId, enabled?: boolean) => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM, transformId], + ({ signal }) => + http.get(addInternalBasePath(`transforms/${transformId}`), { + version: '1', + signal, + }), + { enabled } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform_audit_messages.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transform_audit_messages.ts new file mode 100644 index 0000000000000..3f7559a251275 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform_audit_messages.ts @@ -0,0 +1,39 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformsAuditMessagesResponseSchema } from '../../../common/api_schemas/audit_messages'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import { TransformMessage } from '../../../common/types/messages'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformAuditMessages = ( + transformId: string, + sortField: keyof TransformMessage, + sortDirection: 'asc' | 'desc' +) => { + const { http } = useAppDependencies(); + + const query = { sortField, sortDirection }; + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_AUDIT_MESSAGES, transformId, query], + ({ signal }) => + http.get( + addInternalBasePath(`transforms/${transformId}/messages`), + { + query, + version: '1', + signal, + } + ) + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts new file mode 100644 index 0000000000000..2d3d7cfa9defd --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform_nodes.ts @@ -0,0 +1,41 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformNodesResponseSchema } from '../../../common/api_schemas/transforms'; +import { + addInternalBasePath, + DEFAULT_REFRESH_INTERVAL_MS, + TRANSFORM_REACT_QUERY_KEYS, +} from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformNodes = () => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_NODES], + async ({ signal }) => { + const transformNodes = await http.get( + addInternalBasePath('transforms/_nodes'), + { + version: '1', + signal, + } + ); + + return transformNodes.count; + }, + { + refetchInterval: DEFAULT_REFRESH_INTERVAL_MS, + } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transform_stats.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transform_stats.ts new file mode 100644 index 0000000000000..d2b9d32f25853 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transform_stats.ts @@ -0,0 +1,37 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { GetTransformsStatsResponseSchema } from '../../../common/api_schemas/transforms_stats'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformStats = ( + transformId: TransformId, + enabled?: boolean, + refetchInterval?: number | false +) => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_STATS, transformId], + ({ signal }) => + http.get( + addInternalBasePath(`transforms/${transformId}/_stats`), + { + version: '1', + signal, + } + ), + { enabled, refetchInterval } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts index 55e3bc40360cf..f74f7a5774ded 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts @@ -5,75 +5,64 @@ * 2.0. */ +import { useQuery } from '@tanstack/react-query'; + import type { IHttpFetchError } from '@kbn/core-http-browser'; import { isDefined } from '@kbn/ml-is-defined'; -import { - isGetTransformNodesResponseSchema, - isGetTransformsResponseSchema, - isGetTransformsStatsResponseSchema, -} from '../../../common/api_schemas/type_guards'; -import { TRANSFORM_MODE } from '../../../common/constants'; -import { isTransformStats } from '../../../common/types/transform_stats'; +import type { GetTransformsResponseSchema } from '../../../common/api_schemas/transforms'; +import type { GetTransformsStatsResponseSchema } from '../../../common/api_schemas/transforms_stats'; import { - type TransformListRow, - refreshTransformList$, - REFRESH_TRANSFORM_LIST_STATE, -} from '../common'; + addInternalBasePath, + DEFAULT_REFRESH_INTERVAL_MS, + TRANSFORM_REACT_QUERY_KEYS, + TRANSFORM_MODE, +} from '../../../common/constants'; +import { isTransformStats } from '../../../common/types/transform_stats'; -import { useApi } from './use_api'; +import { type TransformListRow } from '../common'; +import { useAppDependencies } from '../app_dependencies'; import { TRANSFORM_ERROR_TYPE } from '../common/transform'; -export type GetTransforms = (forceRefresh?: boolean) => void; - -export const useGetTransforms = ( - setTransforms: React.Dispatch>, - setTransformNodes: React.Dispatch>, - setErrorMessage: React.Dispatch>, - setTransformIdsWithoutConfig: React.Dispatch>, - setIsInitialized: React.Dispatch>, - blockRefresh: boolean -): GetTransforms => { - const api = useApi(); - - let concurrentLoads = 0; - - const getTransforms = async (forceRefresh = false) => { - if (forceRefresh === true || blockRefresh === false) { - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.LOADING); - concurrentLoads++; - - if (concurrentLoads > 1) { - return; - } - - const fetchOptions = { asSystemRequest: true }; - const transformNodes = await api.getTransformNodes(); - const transformConfigs = await api.getTransforms(fetchOptions); - const transformStats = await api.getTransformsStats(fetchOptions); - - if ( - !isGetTransformsResponseSchema(transformConfigs) || - !isGetTransformsStatsResponseSchema(transformStats) || - !isGetTransformNodesResponseSchema(transformNodes) - ) { - // An error is followed immediately by setting the state to idle. - // This way we're able to treat ERROR as a one-time-event like REFRESH. - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.ERROR); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.IDLE); - setTransformNodes(0); - setTransforms([]); - - setIsInitialized(true); - - if (!isGetTransformsResponseSchema(transformConfigs)) { - setErrorMessage(transformConfigs); - } else if (!isGetTransformsStatsResponseSchema(transformStats)) { - setErrorMessage(transformStats); +interface UseGetTransformsResponse { + transforms: TransformListRow[]; + transformIds: string[]; + transformIdsWithoutConfig?: string[]; +} + +const getInitialData = (): UseGetTransformsResponse => ({ + transforms: [], + transformIds: [], +}); + +interface UseGetTransformsOptions { + enabled?: boolean; +} + +export const useGetTransforms = ({ enabled }: UseGetTransformsOptions = {}) => { + const { http } = useAppDependencies(); + + const { data = getInitialData(), ...rest } = useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORMS], + async ({ signal }) => { + const update = getInitialData(); + + const transformConfigs = await http.get( + addInternalBasePath('transforms'), + { + version: '1', + asSystemRequest: true, + signal, } - - return; - } + ); + const transformStats = await http.get( + addInternalBasePath(`transforms/_stats`), + { + version: '1', + asSystemRequest: true, + signal, + } + ); // There might be some errors with fetching certain transforms // For example, when task exists and is running but the config is deleted @@ -87,17 +76,12 @@ export const useGetTransforms = ( }) .filter(isDefined); - setTransformIdsWithoutConfig( - danglingTaskIdMatches.length > 0 ? danglingTaskIdMatches : undefined - ); - } else { - setTransformIdsWithoutConfig(undefined); + update.transformIdsWithoutConfig = + danglingTaskIdMatches.length > 0 ? danglingTaskIdMatches : undefined; } - const tableRows = transformConfigs.transforms.reduce((reducedtableRows, config) => { - const stats = isGetTransformsStatsResponseSchema(transformStats) - ? transformStats.transforms.find((d) => config.id === d.id) - : undefined; + update.transforms = transformConfigs.transforms.reduce((reducedtableRows, config) => { + const stats = transformStats.transforms.find((d) => config.id === d.id); // A newly created transform might not have corresponding stats yet. // If that's the case we just skip the transform and don't add it to the transform list yet. @@ -117,21 +101,15 @@ export const useGetTransforms = ( return reducedtableRows; }, [] as TransformListRow[]); - setTransformNodes(transformNodes.count); - setTransforms(tableRows); - setErrorMessage(undefined); - setIsInitialized(true); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.IDLE); - - concurrentLoads--; + update.transformIds = update.transforms.map(({ id }) => id); - if (concurrentLoads > 0) { - concurrentLoads = 0; - getTransforms(true); - return; - } + return update; + }, + { + enabled, + refetchInterval: DEFAULT_REFRESH_INTERVAL_MS, } - }; + ); - return getTransforms; + return { data, ...rest }; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transforms_preview.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transforms_preview.ts new file mode 100644 index 0000000000000..ae671912b9267 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transforms_preview.ts @@ -0,0 +1,36 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; + +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import type { + PostTransformsPreviewRequestSchema, + PostTransformsPreviewResponseSchema, +} from '../../../common/api_schemas/transforms'; +import { addInternalBasePath, TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useGetTransformsPreview = ( + obj: PostTransformsPreviewRequestSchema, + enabled?: boolean +) => { + const { http } = useAppDependencies(); + + return useQuery( + [TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORMS_PREVIEW, obj], + ({ signal }) => + http.post(addInternalBasePath('transforms/_preview'), { + body: JSON.stringify(obj), + version: '1', + signal, + }), + { enabled } + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx index 9da9fa8e5e782..ced29e2f8c17c 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React, { FC } from 'react'; -import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; - +import React, { type FC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import '@testing-library/jest-dom/extend-expect'; import { render, screen, waitFor } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { CoreSetup } from '@kbn/core/public'; import { DataGrid, type UseIndexDataReturnType } from '@kbn/ml-data-grid'; import type { RuntimeMappings } from '@kbn/ml-runtime-field-utils'; @@ -25,7 +25,6 @@ import { useIndexData } from './use_index_data'; jest.mock('../../shared_imports'); jest.mock('../app_dependencies'); -jest.mock('./use_api'); import { MlSharedContext } from '../__mocks__/shared_context'; @@ -45,13 +44,17 @@ const runtimeMappings: RuntimeMappings = { }, }; +const queryClient = new QueryClient(); + describe('Transform: useIndexData()', () => { test('dataView set triggers loading', async () => { const mlShared = await getMlSharedImports(); const wrapper: FC = ({ children }) => ( - - {children} - + + + {children} + + ); const { result, waitForNextUpdate } = renderHook( @@ -102,11 +105,13 @@ describe('Transform: with useIndexData()', () => { }; render( - - - - - + + + + + + + ); // Act @@ -142,11 +147,13 @@ describe('Transform: with useIndexData()', () => { }; render( - - - - - + + + + + + + ); // Act diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 3a6781615b70b..4534552f6b405 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { EuiDataGridColumn } from '@elastic/eui'; @@ -28,10 +28,6 @@ import { } from '@kbn/ml-data-grid'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; -import { - isEsSearchResponse, - isFieldHistogramsResponseSchema, -} from '../../../common/api_schemas/type_guards'; import { hasKeywordDuplicate, isKeywordDuplicate, @@ -44,7 +40,7 @@ import { useToastNotifications, useAppDependencies } from '../app_dependencies'; import type { StepDefineExposedState } from '../sections/create_transform/components/step_define/common'; import { SearchItems } from './use_search_items'; -import { useApi } from './use_api'; +import { useGetHistogramsForFields } from './use_get_histograms_for_fields'; import { useDataSearch } from './use_data_search'; export const useIndexData = ( @@ -52,7 +48,7 @@ export const useIndexData = ( query: TransformConfigQuery, combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'], timeRangeMs?: TimeRangeMs, - populatedFields?: Set | null + populatedFields?: string[] ): UseIndexDataReturnType => { const { analytics } = useAppDependencies(); @@ -61,13 +57,8 @@ export const useIndexData = ( const loadIndexDataStartTime = useRef(window.performance.now()); const indexPattern = useMemo(() => dataView.getIndexPattern(), [dataView]); - - const api = useApi(); - const dataSearch = useDataSearch(); const toastNotifications = useToastNotifications(); - const [dataViewFields, setDataViewFields] = useState(); - const baseFilterCriteria = buildBaseFilterCriteria( dataView.timeFieldName, timeRangeMs?.from, @@ -86,73 +77,73 @@ export const useIndexData = ( }, }; - useEffect(() => { - if (dataView.timeFieldName !== undefined && timeRangeMs === undefined) { - return; - } - const abortController = new AbortController(); - - // Fetch 500 random documents to determine populated fields. - // This is a workaround to avoid passing potentially thousands of unpopulated fields - // (for example, as part of filebeat/metricbeat/ECS based indices) - // to the data grid component which would significantly slow down the page. - const fetchDataGridSampleDocuments = async function () { - let populatedDataViewFields = populatedFields ? [...populatedFields] : []; - let isMissingFields = populatedDataViewFields.length === 0; - - // If populatedFields are not provided, make own request to calculate - if (populatedFields === undefined) { - setErrorMessage(''); - setStatus(INDEX_STATUS.LOADING); - - const esSearchRequest = { - index: indexPattern, - body: { - fields: ['*'], - _source: false, - query: { - function_score: { - query: defaultQuery, - random_score: {}, - }, - }, - size: 500, + // Fetch 500 random documents to determine populated fields. + // This is a workaround to avoid passing potentially thousands of unpopulated fields + // (for example, as part of filebeat/metricbeat/ECS based indices) + // to the data grid component which would significantly slow down the page. + const { + error: dataViewFieldsError, + data: dataViewFieldsData, + isError: dataViewFieldsIsError, + isLoading: dataViewFieldsIsLoading, + } = useDataSearch( + { + index: indexPattern, + body: { + fields: ['*'], + _source: false, + query: { + function_score: { + query: defaultQuery, + random_score: {}, }, - }; - - const resp = await dataSearch(esSearchRequest, abortController.signal); - - if (!isEsSearchResponse(resp)) { - setErrorMessage(getErrorMessage(resp)); - setStatus(INDEX_STATUS.ERROR); - return; - } - const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); - isMissingFields = resp.hits.hits.every((d) => typeof d.fields === 'undefined'); + }, + size: 500, + }, + }, + // Check whether fetching should be enabled + // If populatedFields are not provided, make own request to calculate + !Array.isArray(populatedFields) && + !(dataView.timeFieldName !== undefined && timeRangeMs === undefined) + ); - populatedDataViewFields = [...new Set(docs.map(Object.keys).flat(1))]; - } + useEffect(() => { + if (dataViewFieldsIsLoading && !dataViewFieldsIsError) { + setErrorMessage(''); + setStatus(INDEX_STATUS.LOADING); + } else if (dataViewFieldsError !== null) { + setErrorMessage(getErrorMessage(dataViewFieldsError)); + setStatus(INDEX_STATUS.ERROR); + } else if ( + !dataViewFieldsIsLoading && + !dataViewFieldsIsError && + dataViewFieldsData !== undefined + ) { const isCrossClusterSearch = indexPattern.includes(':'); - - // Get all field names for each returned doc and flatten it - // to a list of unique field names used across all docs. - const allDataViewFields = getFieldsFromKibanaIndexPattern(dataView); - const filteredDataViewFields = populatedDataViewFields - .filter((d) => allDataViewFields.includes(d)) - .sort(); + const isMissingFields = dataViewFieldsData.hits.hits.every( + (d) => typeof d.fields === 'undefined' + ); setCcsWarning(isCrossClusterSearch && isMissingFields); setStatus(INDEX_STATUS.LOADED); - setDataViewFields(filteredDataViewFields); - }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataViewFieldsData, dataViewFieldsError, dataViewFieldsIsError, dataViewFieldsIsLoading]); - fetchDataGridSampleDocuments(); + const dataViewFields = useMemo(() => { + let allPopulatedFields = Array.isArray(populatedFields) ? populatedFields : []; - return () => { - abortController.abort(); - }; + if (populatedFields === undefined && dataViewFieldsData) { + // Get all field names for each returned doc and flatten it + // to a list of unique field names used across all docs. + const docs = dataViewFieldsData.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); + allPopulatedFields = [...new Set(docs.map(Object.keys).flat(1))]; + } + + const allDataViewFields = getFieldsFromKibanaIndexPattern(dataView); + return allPopulatedFields.filter((d) => allDataViewFields.includes(d)).sort(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [timeRangeMs, populatedFields?.size]); + }, [dataViewFieldsData, populatedFields]); const columns: EuiDataGridColumn[] = useMemo(() => { if (typeof dataViewFields === 'undefined') { @@ -206,132 +197,113 @@ export const useIndexData = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify([query, timeRangeMs])]); - useEffect(() => { - if (typeof dataViewFields === 'undefined') { - return; - } - const abortController = new AbortController(); + const sort: EsSorting = sortingColumns.reduce((s, column) => { + s[column.id] = { order: column.direction }; + return s; + }, {} as EsSorting); - const fetchDataGridData = async function () { + const { + error: dataGridDataError, + data: dataGridData, + isError: dataGridDataIsError, + isLoading: dataGridDataIsLoading, + } = useDataSearch( + { + index: indexPattern, + body: { + fields: ['*'], + _source: false, + query: isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, + from: pagination.pageIndex * pagination.pageSize, + size: pagination.pageSize, + ...(Object.keys(sort).length > 0 ? { sort } : {}), + ...(isRuntimeMappings(combinedRuntimeMappings) + ? { runtime_mappings: combinedRuntimeMappings } + : {}), + }, + }, + // Check whether fetching should be enabled + dataViewFields !== undefined + ); + + useEffect(() => { + if (dataGridDataIsLoading && !dataGridDataIsError) { setErrorMessage(''); setStatus(INDEX_STATUS.LOADING); - - const sort: EsSorting = sortingColumns.reduce((s, column) => { - s[column.id] = { order: column.direction }; - return s; - }, {} as EsSorting); - - const esSearchRequest = { - index: indexPattern, - body: { - fields: ['*'], - _source: false, - query: isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, - from: pagination.pageIndex * pagination.pageSize, - size: pagination.pageSize, - ...(Object.keys(sort).length > 0 ? { sort } : {}), - ...(isRuntimeMappings(combinedRuntimeMappings) - ? { runtime_mappings: combinedRuntimeMappings } - : {}), - }, - }; - const resp = await dataSearch(esSearchRequest, abortController.signal); - - if (!isEsSearchResponse(resp)) { - setErrorMessage(getErrorMessage(resp)); - setStatus(INDEX_STATUS.ERROR); - return; - } - + } else if (dataGridDataError !== null) { + setErrorMessage(getErrorMessage(dataGridDataError)); + setStatus(INDEX_STATUS.ERROR); + } else if (!dataGridDataIsLoading && !dataGridDataIsError && dataGridData !== undefined) { const isCrossClusterSearch = indexPattern.includes(':'); - const isMissingFields = resp.hits.hits.every((d) => typeof d.fields === 'undefined'); + const isMissingFields = dataGridData.hits.hits.every((d) => typeof d.fields === 'undefined'); - const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); + const docs = dataGridData.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); setCcsWarning(isCrossClusterSearch && isMissingFields); setRowCountInfo({ - rowCount: typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total!.value, + rowCount: + typeof dataGridData.hits.total === 'number' + ? dataGridData.hits.total + : dataGridData.hits.total!.value, rowCountRelation: - typeof resp.hits.total === 'number' + typeof dataGridData.hits.total === 'number' ? ('eq' as estypes.SearchTotalHitsRelation) - : resp.hits.total!.relation, + : dataGridData.hits.total!.relation, }); setTableItems(docs); setStatus(INDEX_STATUS.LOADED); - }; - - fetchDataGridData(); - - return () => { - abortController.abort(); - }; - // custom comparison - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - indexPattern, + } // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify([ - query, - pagination, - sortingColumns, - dataViewFields, + }, [dataGridDataError, dataGridDataIsError, dataGridDataIsLoading]); + + const allDataViewFieldNames = new Set(dataView.fields.map((f) => f.name)); + const { error: histogramsForFieldsError, data: histogramsForFieldsData } = + useGetHistogramsForFields( + indexPattern, + columns + .filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + .map((cT) => { + // If a column field name has a corresponding keyword field, + // fetch the keyword field instead to be able to do aggregations. + const fieldName = cT.id; + return hasKeywordDuplicate(fieldName, allDataViewFieldNames) + ? { + fieldName: `${fieldName}.keyword`, + type: getFieldType(undefined), + } + : { + fieldName, + type: getFieldType(cT.schema), + }; + }), + isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, combinedRuntimeMappings, - timeRangeMs, - ]), - ]); + chartsVisible + ); useEffect(() => { - const fetchColumnChartsData = async function () { - const allDataViewFieldNames = new Set(dataView.fields.map((f) => f.name)); - const columnChartsData = await api.getHistogramsForFields( - indexPattern, - columns - .filter((cT) => dataGrid.visibleColumns.includes(cT.id)) - .map((cT) => { - // If a column field name has a corresponding keyword field, - // fetch the keyword field instead to be able to do aggregations. - const fieldName = cT.id; - return hasKeywordDuplicate(fieldName, allDataViewFieldNames) - ? { - fieldName: `${fieldName}.keyword`, - type: getFieldType(undefined), - } - : { - fieldName, - type: getFieldType(cT.schema), - }; - }), - isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, - combinedRuntimeMappings - ); - - if (!isFieldHistogramsResponseSchema(columnChartsData)) { - showDataGridColumnChartErrorMessageToast(columnChartsData, toastNotifications); - return; - } + if (histogramsForFieldsError !== null) { + showDataGridColumnChartErrorMessageToast(histogramsForFieldsError, toastNotifications); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [histogramsForFieldsError]); + useEffect(() => { + if (histogramsForFieldsData) { setColumnCharts( // revert field names with `.keyword` used to do aggregations to their original column name - columnChartsData.map((d) => ({ + histogramsForFieldsData.map((d) => ({ ...d, ...(isKeywordDuplicate(d.id, allDataViewFieldNames) ? { id: removeKeywordPostfix(d.id) } : {}), })) ); - }; - - if (chartsVisible) { - fetchColumnChartsData(); } // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - chartsVisible, - indexPattern, - // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify([query, dataGrid.visibleColumns, combinedRuntimeMappings, timeRangeMs]), - ]); + }, [histogramsForFieldsData]); const renderCellValue = useRenderCellValue(dataView, pagination, tableItems); diff --git a/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx index af6018c35cecc..9ecd1b8717243 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx @@ -6,31 +6,39 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { StartTransformsRequestSchema } from '../../../common/api_schemas/start_transforms'; -import { isStartTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { addInternalBasePath } from '../../../common/constants'; import { getErrorMessage } from '../../../common/utils/errors'; +import type { + ReauthorizeTransformsRequestSchema, + ReauthorizeTransformsResponseSchema, +} from '../../../common/api_schemas/reauthorize_transforms'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useReauthorizeTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - - return async (transformsInfo: StartTransformsRequestSchema) => { - const results = await api.reauthorizeTransforms(transformsInfo); - if (!isStartTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: ReauthorizeTransformsRequestSchema) => + http.post( + addInternalBasePath('reauthorize_transforms'), + { + body: JSON.stringify(reqBody), + version: '1', + } + ), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.stepCreateForm.reauthorizeTransformResponseSchemaErrorMessage', @@ -38,44 +46,37 @@ export const useReauthorizeTransforms = () => { defaultMessage: 'An error occurred calling the reauthorize transforms request.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const result = results[transformId]; - if (result.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.reauthorizeTransformSuccessMessage', { - defaultMessage: 'Request to reauthorize transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - title: i18n.translate( - 'xpack.transform.transformList.reauthorizeTransformErrorMessage', + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const result = results[transformId]; + if (!result.success) { + toastNotifications.addError( + new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - defaultMessage: 'An error occurred reauthorizing the transform {transformId}', - values: { transformId }, + title: i18n.translate( + 'xpack.transform.transformList.reauthorizeTransformErrorMessage', + { + defaultMessage: 'An error occurred reauthorizing the transform {transformId}', + values: { transformId }, + } + ), + toastMessage: result.error!.reason, } - ), - toastMessage: result.error!.reason, - }); + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_refresh_transform_list.ts b/x-pack/plugins/transform/public/app/hooks/use_refresh_transform_list.ts new file mode 100644 index 0000000000000..651886ba76f7b --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_refresh_transform_list.ts @@ -0,0 +1,20 @@ +/* + * 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 { useQueryClient } from '@tanstack/react-query'; + +import { TRANSFORM_REACT_QUERY_KEYS } from '../../../common/constants'; + +export const useRefreshTransformList = () => { + const queryClient = useQueryClient(); + + return () => { + queryClient.invalidateQueries([TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_NODES]); + queryClient.invalidateQueries([TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORMS]); + queryClient.invalidateQueries([TRANSFORM_REACT_QUERY_KEYS.GET_TRANSFORM_AUDIT_MESSAGES]); + }; +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx index c33cec3f5b93f..1f415eae1ad20 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx @@ -6,107 +6,72 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; + import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; + import type { - ResetTransformStatus, ResetTransformsRequestSchema, + ResetTransformsResponseSchema, } from '../../../common/api_schemas/reset_transforms'; -import { isResetTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; +import { addInternalBasePath } from '../../../common/constants'; import { getErrorMessage } from '../../../common/utils/errors'; + import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$ } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; -type SuccessCountField = keyof Omit; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useResetTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - - return async (reqBody: ResetTransformsRequestSchema) => { - const results = await api.resetTransforms(reqBody); - if (!isResetTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: ResetTransformsRequestSchema) => + http.post(addInternalBasePath('reset_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate('xpack.transform.transformList.resetTransformGenericErrorMessage', { defaultMessage: 'An error occurred calling the API endpoint to reset transforms.', }), text: toMountPoint( - , - { theme$: theme.theme$ } + , + { + theme, + i18n: i18nStart, + } ), - }); - return; - } - - const isBulk = Object.keys(results).length > 1; - const successCount: Record = { - transformReset: 0, - }; - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const status = results[transformId]; + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const status = results[transformId]; - // if we are only resetting one transform, show the success toast messages - if (!isBulk && status.transformReset) { - if (status.transformReset?.success) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.resetTransformSuccessMessage', { - defaultMessage: 'Request to reset transform {transformId} acknowledged.', + if (status.transformReset?.error) { + const error = status.transformReset.error.reason; + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.transformList.resetTransformErrorMessage', { + defaultMessage: 'An error occurred resetting the transform {transformId}', values: { transformId }, - }) - ); + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); } - } else { - (Object.keys(successCount) as SuccessCountField[]).forEach((key) => { - if (status[key]?.success) { - successCount[key] = successCount[key] + 1; - } - }); - } - if (status.transformReset?.error) { - const error = status.transformReset.error.reason; - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.transformList.resetTransformErrorMessage', { - defaultMessage: 'An error occurred resetting the transform {transformId}', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); } } - } - // if we are deleting multiple transforms, combine the success messages - if (isBulk) { - if (successCount.transformReset > 0) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.bulkResetTransformSuccessMessage', { - defaultMessage: - 'Successfully reset {count} {count, plural, one {transform} other {transforms}}.', - values: { count: successCount.transformReset }, - }) - ); - } - } + refreshTransformList(); + }, + }); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx index 8454580867391..bb568673a5758 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx @@ -6,31 +6,38 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { ScheduleNowTransformsRequestSchema } from '../../../common/api_schemas/schedule_now_transforms'; -import { isScheduleNowTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; - +import { addInternalBasePath } from '../../../common/constants'; +import type { + ScheduleNowTransformsRequestSchema, + ScheduleNowTransformsResponseSchema, +} from '../../../common/api_schemas/schedule_now_transforms'; import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useScheduleNowTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - return async (transformsInfo: ScheduleNowTransformsRequestSchema) => { - const results = await api.scheduleNowTransforms(transformsInfo); - - if (!isScheduleNowTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: ScheduleNowTransformsRequestSchema) => + http.post( + addInternalBasePath('schedule_now_transforms'), + { + body: JSON.stringify(reqBody), + version: '1', + } + ), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.stepCreateForm.scheduleNowTransformResponseSchemaErrorMessage', @@ -39,46 +46,38 @@ export const useScheduleNowTransforms = () => { 'An error occurred calling the request to schedule the transform to process data instantly.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const result = results[transformId]; - if (result.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.scheduleNowTransformSuccessMessage', { - defaultMessage: - 'Request to schedule transform {transformId} to process data instantly acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - title: i18n.translate( - 'xpack.transform.transformList.scheduleNowTransformErrorMessage', + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const result = results[transformId]; + if (!result.success) { + toastNotifications.addError( + new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - defaultMessage: - 'An error occurred scheduling transform {transformId} to process data instantly.', - values: { transformId }, + title: i18n.translate( + 'xpack.transform.transformList.scheduleNowTransformErrorMessage', + { + defaultMessage: + 'An error occurred scheduling transform {transformId} to process data instantly.', + values: { transformId }, + } + ), + toastMessage: result.error!.reason, } - ), - toastMessage: result.error!.reason, - }); + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx index e4d9bdfe431a9..104c3145fc259 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx @@ -6,31 +6,35 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { StartTransformsRequestSchema } from '../../../common/api_schemas/start_transforms'; -import { isStartTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; - +import { addInternalBasePath } from '../../../common/constants'; +import type { + StartTransformsRequestSchema, + StartTransformsResponseSchema, +} from '../../../common/api_schemas/start_transforms'; import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useStartTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - return async (transformsInfo: StartTransformsRequestSchema) => { - const results = await api.startTransforms(transformsInfo); - - if (!isStartTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: StartTransformsRequestSchema) => + http.post(addInternalBasePath('start_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.stepCreateForm.startTransformResponseSchemaErrorMessage', @@ -38,41 +42,34 @@ export const useStartTransforms = () => { defaultMessage: 'An error occurred calling the start transforms request.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - const result = results[transformId]; - if (result.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.startTransformSuccessMessage', { - defaultMessage: 'Request to start transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), { - title: i18n.translate('xpack.transform.transformList.startTransformErrorMessage', { - defaultMessage: 'An error occurred starting the transform {transformId}', - values: { transformId }, - }), - toastMessage: result.error!.reason, - }); + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const result = results[transformId]; + if (!result.success) { + toastNotifications.addError( + new Error(JSON.stringify(result.error!.caused_by, null, 2)), + { + title: i18n.translate('xpack.transform.transformList.startTransformErrorMessage', { + defaultMessage: 'An error occurred starting the transform {transformId}', + values: { transformId }, + }), + toastMessage: result.error!.reason, + } + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx index 39d009e471180..564b17feac3f9 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx @@ -6,31 +6,36 @@ */ import React from 'react'; +import { useMutation } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; - -import type { StopTransformsRequestSchema } from '../../../common/api_schemas/stop_transforms'; -import { isStopTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { addInternalBasePath } from '../../../common/constants'; +import type { + StopTransformsRequestSchema, + StopTransformsResponseSchema, +} from '../../../common/api_schemas/stop_transforms'; import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; -import { useApi } from './use_api'; +import { useRefreshTransformList } from './use_refresh_transform_list'; export const useStopTransforms = () => { - const { overlays, theme } = useAppDependencies(); + const { http, i18n: i18nStart, theme } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); - const api = useApi(); - - return async (transformsInfo: StopTransformsRequestSchema) => { - const results = await api.stopTransforms(transformsInfo); - if (!isStopTransformsResponseSchema(results)) { + const mutation = useMutation({ + mutationFn: (reqBody: StopTransformsRequestSchema) => + http.post(addInternalBasePath('stop_transforms'), { + body: JSON.stringify(reqBody), + version: '1', + }), + onError: (error) => toastNotifications.addDanger({ title: i18n.translate( 'xpack.transform.transformList.stopTransformResponseSchemaErrorMessage', @@ -38,39 +43,29 @@ export const useStopTransforms = () => { defaultMessage: 'An error occurred called the stop transforms request.', } ), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - return; - } - - for (const transformId in results) { - // hasOwnProperty check to ensure only properties on object itself, and not its prototypes - if (results.hasOwnProperty(transformId)) { - if (results[transformId].success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.stopTransformSuccessMessage', { - defaultMessage: 'Request to stop data frame transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - } else { - toastNotifications.addDanger( - i18n.translate('xpack.transform.transformList.stopTransformErrorMessage', { - defaultMessage: 'An error occurred stopping the data frame transform {transformId}', - values: { transformId }, - }) - ); + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }), + onSuccess: (results) => { + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + if (!results[transformId].success) { + toastNotifications.addDanger( + i18n.translate('xpack.transform.transformList.stopTransformErrorMessage', { + defaultMessage: 'An error occurred stopping the data frame transform {transformId}', + values: { transformId }, + }) + ); + } } } - } - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - }; + refreshTransformList(); + }, + }); + + return mutation.mutate; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_transform_capabilities.ts b/x-pack/plugins/transform/public/app/hooks/use_transform_capabilities.ts new file mode 100644 index 0000000000000..f497da3bd51de --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_transform_capabilities.ts @@ -0,0 +1,23 @@ +/* + * 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 { + getInitialTransformCapabilities, + isTransformCapabilities, +} from '../../../common/types/capabilities'; + +import { useAppDependencies } from '../app_dependencies'; + +export const useTransformCapabilities = () => { + const { application } = useAppDependencies(); + + if (isTransformCapabilities(application?.capabilities?.transform)) { + return application.capabilities.transform; + } + + return getInitialTransformCapabilities(); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts index f96a0f72194c6..0871dd7877c14 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts @@ -29,14 +29,13 @@ import { } from '@kbn/ml-data-grid'; import type { PreviewMappingsProperties } from '../../../common/api_schemas/transforms'; -import { isPostTransformsPreviewResponseSchema } from '../../../common/api_schemas/type_guards'; import { getErrorMessage } from '../../../common/utils/errors'; import { getPreviewTransformRequestBody, type TransformConfigQuery } from '../common'; import { SearchItems } from './use_search_items'; -import { useApi } from './use_api'; +import { useGetTransformsPreview } from './use_get_transforms_preview'; import { StepDefineExposedState } from '../sections/create_transform/components/step_define'; import { isLatestPartialRequest, @@ -111,7 +110,6 @@ export const useTransformConfigData = ( ): UseIndexDataReturnType => { const [previewMappingsProperties, setPreviewMappingsProperties] = useState({}); - const api = useApi(); // Filters mapping properties of type `object`, which get returned for nested field parents. const columnKeys = Object.keys(previewMappingsProperties).filter( @@ -147,84 +145,100 @@ export const useTransformConfigData = ( tableItems, } = dataGrid; - const getPreviewData = async () => { - if (!validationStatus.isValid) { + const previewRequest = useMemo( + () => + getPreviewTransformRequestBody( + dataView, + query, + requestPayload, + combinedRuntimeMappings, + timeRangeMs + ), + [dataView, query, requestPayload, combinedRuntimeMappings, timeRangeMs] + ); + + const { + error: previewError, + data: previewData, + isError, + isLoading, + } = useGetTransformsPreview(previewRequest, validationStatus.isValid); + + useEffect(() => { + if (isLoading) { + setErrorMessage(''); + setNoDataMessage(''); + setStatus(INDEX_STATUS.LOADING); + } else if (isError) { + setErrorMessage(getErrorMessage(previewError)); setTableItems([]); setRowCountInfo({ rowCount: 0, rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, }); - setNoDataMessage(validationStatus.errorMessage!); - return; + setPreviewMappingsProperties({}); + setStatus(INDEX_STATUS.ERROR); + } else if (!isLoading && !isError && previewData !== undefined) { + // To improve UI performance with a latest configuration for indices with a large number + // of fields, we reduce the number of available columns to those populated with values. + + // 1. Flatten the returned object structure object documents to match mapping properties + const docs = previewData.preview.map(getFlattenedObject); + + // 2. Get all field names for each returned doc and flatten it + // to a list of unique field names used across all docs. + const populatedFields = [...new Set(docs.map(Object.keys).flat(1))]; + + // 3. Filter mapping properties by populated fields + let populatedProperties: PreviewMappingsProperties = Object.entries( + previewData.generated_dest_index.mappings.properties + ) + .filter(([key]) => populatedFields.includes(key)) + .reduce( + (p, [key, value]) => ({ + ...p, + [key]: value, + }), + {} + ); + + populatedProperties = getCombinedProperties(populatedProperties, docs); + + setTableItems(docs); + setRowCountInfo({ + rowCount: docs.length, + rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, + }); + setPreviewMappingsProperties(populatedProperties); + setStatus(INDEX_STATUS.LOADED); + + if (docs.length === 0) { + setNoDataMessage( + i18n.translate('xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', { + defaultMessage: + 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', + }) + ); + } else { + setNoDataMessage(''); + } } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isError, isLoading, previewData]); - setErrorMessage(''); - setNoDataMessage(''); - setStatus(INDEX_STATUS.LOADING); - - const previewRequest = getPreviewTransformRequestBody( - dataView, - query, - requestPayload, - combinedRuntimeMappings, - timeRangeMs - ); - const resp = await api.getTransformsPreview(previewRequest); - - if (!isPostTransformsPreviewResponseSchema(resp)) { - setErrorMessage(getErrorMessage(resp)); + useEffect(() => { + if (!validationStatus.isValid) { setTableItems([]); setRowCountInfo({ rowCount: 0, rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, }); - setPreviewMappingsProperties({}); - setStatus(INDEX_STATUS.ERROR); - return; - } - - // To improve UI performance with a latest configuration for indices with a large number - // of fields, we reduce the number of available columns to those populated with values. - - // 1. Flatten the returned object structure object documents to match mapping properties - const docs = resp.preview.map(getFlattenedObject); - - // 2. Get all field names for each returned doc and flatten it - // to a list of unique field names used across all docs. - const populatedFields = [...new Set(docs.map(Object.keys).flat(1))]; - - // 3. Filter mapping properties by populated fields - let populatedProperties: PreviewMappingsProperties = Object.entries( - resp.generated_dest_index.mappings.properties - ) - .filter(([key]) => populatedFields.includes(key)) - .reduce( - (p, [key, value]) => ({ - ...p, - [key]: value, - }), - {} - ); - - populatedProperties = getCombinedProperties(populatedProperties, docs); - - setTableItems(docs); - setRowCountInfo({ - rowCount: docs.length, - rowCountRelation: ES_CLIENT_TOTAL_HITS_RELATION.EQ, - }); - setPreviewMappingsProperties(populatedProperties); - setStatus(INDEX_STATUS.LOADED); - - if (docs.length === 0) { - setNoDataMessage( - i18n.translate('xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', { - defaultMessage: - 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', - }) - ); + setNoDataMessage(validationStatus.errorMessage!); } - }; + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [validationStatus.isValid]); useEffect(() => { resetPagination(); @@ -232,15 +246,6 @@ export const useTransformConfigData = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(query)]); - useEffect(() => { - getPreviewData(); - // custom comparison - /* eslint-disable react-hooks/exhaustive-deps */ - }, [ - dataView.getIndexPattern(), - JSON.stringify([requestPayload, query, combinedRuntimeMappings, timeRangeMs]), - ]); - if (sortingColumns.length > 0) { const sortingColumnsWithTypes = sortingColumns.map((c) => { // Since items might contain undefined/null values, we want to accurate find the data type @@ -291,13 +296,7 @@ export const useTransformConfigData = ( return cellValue; }; - }, [ - pageData, - pagination.pageIndex, - pagination.pageSize, - previewMappingsProperties, - formatHumanReadableDateTimeSeconds, - ]); + }, [pageData, pagination.pageIndex, pagination.pageSize, previewMappingsProperties]); return { ...dataGrid, diff --git a/x-pack/plugins/transform/public/app/hooks/use_update_transform.ts b/x-pack/plugins/transform/public/app/hooks/use_update_transform.ts new file mode 100644 index 0000000000000..3859f9a8353f0 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_update_transform.ts @@ -0,0 +1,41 @@ +/* + * 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 { useMutation } from '@tanstack/react-query'; + +import type { + PostTransformsUpdateRequestSchema, + PostTransformsUpdateResponseSchema, +} from '../../../common/api_schemas/update_transforms'; +import { addInternalBasePath } from '../../../common/constants'; +import type { TransformId } from '../../../common/types/transform'; + +import { useAppDependencies } from '../app_dependencies'; + +import { useRefreshTransformList } from './use_refresh_transform_list'; + +export const useUpdateTransform = ( + transformId: TransformId, + transformConfig: PostTransformsUpdateRequestSchema +) => { + const { http } = useAppDependencies(); + const refreshTransformList = useRefreshTransformList(); + + const mutation = useMutation({ + mutationFn: () => + http.post( + addInternalBasePath(`transforms/${transformId}/_update`), + { + body: JSON.stringify(transformConfig), + version: '1', + } + ), + onSuccess: () => refreshTransformList(), + }); + + return mutation.mutate; +}; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx deleted file mode 100644 index 02bbe4e40a969..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 React, { createContext } from 'react'; -import { useQuery } from '@tanstack/react-query'; - -import type { IHttpFetchError } from '@kbn/core-http-browser'; - -import type { Privileges } from '../../../../../common/types/privileges'; - -import { - type PrivilegesAndCapabilities, - type TransformCapabilities, - INITIAL_CAPABILITIES, -} from '../../../../../common/privilege/has_privilege_factory'; - -import { useAppDependencies } from '../../../app_dependencies'; - -interface Authorization { - isLoading: boolean; - apiError: Error | null; - privileges: Privileges; - capabilities: TransformCapabilities; -} - -const initialValue: Authorization = { - isLoading: true, - apiError: null, - privileges: { - hasAllPrivileges: false, - missingPrivileges: {}, - }, - capabilities: INITIAL_CAPABILITIES, -}; - -export const AuthorizationContext = createContext({ ...initialValue }); - -interface Props { - privilegesEndpoint: { path: string; version: string }; - children: React.ReactNode; -} - -export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => { - const { http } = useAppDependencies(); - - const { path, version } = privilegesEndpoint; - - const { - isLoading, - error, - data: privilegesData, - } = useQuery( - ['transform-privileges-and-capabilities'], - async ({ signal }) => { - return await http.fetch(path, { - version, - method: 'GET', - signal, - }); - } - ); - - const value = { - isLoading, - privileges: - isLoading || privilegesData === undefined - ? { ...initialValue.privileges } - : privilegesData.privileges, - capabilities: - isLoading || privilegesData === undefined - ? { ...INITIAL_CAPABILITIES } - : privilegesData.capabilities, - apiError: error ? error : null, - }; - - return ( - {children} - ); -}; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/index.ts b/x-pack/plugins/transform/public/app/lib/authorization/components/index.ts deleted file mode 100644 index cb0f248efc165..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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. - */ - -export { createCapabilityFailureMessage } from '../../../../../common/privilege/has_privilege_factory'; -export { AuthorizationProvider, AuthorizationContext } from './authorization_provider'; -export { PrivilegesWrapper } from './with_privileges'; -export { NotAuthorizedSection } from './not_authorized_section'; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/not_authorized_section.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/not_authorized_section.tsx deleted file mode 100644 index 1ab942d59c787..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/not_authorized_section.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 React from 'react'; -import { EuiPageTemplate } from '@elastic/eui'; - -interface Props { - title: React.ReactNode; - message: React.ReactNode | string; -} - -export const NotAuthorizedSection = ({ title, message }: Props) => ( - {title}} - body={

{message}

} - /> -); diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx deleted file mode 100644 index a8069b2156239..0000000000000 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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 React, { useContext, FC } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { MissingPrivileges } from '../../../../../common/types/privileges'; -import { SectionLoading } from '../../../components'; -import { AuthorizationContext } from './authorization_provider'; -import { NotAuthorizedSection } from './not_authorized_section'; -import { - hasPrivilegeFactory, - toArray, - Privilege, -} from '../../../../../common/privilege/has_privilege_factory'; - -interface Props { - /** - * Each required privilege must have the format "section.privilege". - * To indicate that *all* privileges from a section are required, we can use the asterix - * e.g. "index.*" - */ - privileges: string | string[]; - children: (childrenProps: { - isLoading: boolean; - hasPrivileges: boolean; - privilegesMissing: MissingPrivileges; - }) => JSX.Element; -} - -export const WithPrivileges = ({ privileges: requiredPrivileges, children }: Props) => { - const { isLoading, privileges } = useContext(AuthorizationContext); - - const privilegesToArray: Privilege[] = toArray(requiredPrivileges).map((p) => { - const [section, privilege] = p.split('.'); - if (!privilege) { - // Oh! we forgot to use the dot "." notation. - throw new Error('Required privilege must have the format "section.privilege"'); - } - return [section, privilege]; - }); - - const hasPrivilege = hasPrivilegeFactory(privileges); - const hasPrivileges = isLoading ? false : privilegesToArray.every(hasPrivilege); - - const privilegesMissing = privilegesToArray.reduce((acc, [section, privilege]) => { - if (privilege === '*') { - acc[section] = privileges.missingPrivileges[section] || []; - } else if ( - privileges.missingPrivileges[section] && - privileges.missingPrivileges[section]!.includes(privilege) - ) { - const missing: string[] = acc[section] || []; - acc[section] = [...missing, privilege]; - } - - return acc; - }, {} as MissingPrivileges); - - return children({ isLoading, hasPrivileges, privilegesMissing }); -}; - -interface MissingClusterPrivilegesProps { - missingPrivileges: string; - privilegesCount: number; -} - -const MissingClusterPrivileges: FC = ({ - missingPrivileges, - privilegesCount, -}) => ( - - } - message={ - - } - /> -); - -export const PrivilegesWrapper: FC<{ privileges: string | string[] }> = ({ - children, - privileges, -}) => ( - - {({ isLoading, hasPrivileges, privilegesMissing }) => { - if (isLoading) { - return ( - - - - ); - } - - if (!hasPrivileges) { - return ( - - ); - } - - return <>{children}; - }} - -); diff --git a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx index 63964bc422130..47fa44a53d182 100644 --- a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx @@ -13,16 +13,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiCallOut, EuiPageTemplate, EuiSpacer } from '@elastic/eui'; -import { isHttpFetchError } from '@kbn/core-http-browser'; -import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants'; + import { TransformConfigUnion } from '../../../../common/types/transform'; -import { useApi } from '../../hooks/use_api'; +import { useGetTransform } from '../../hooks'; import { useDocumentationLinks } from '../../hooks/use_documentation_links'; import { useSearchItems } from '../../hooks/use_search_items'; import { BREADCRUMB_SECTION, breadcrumbService, docTitleService } from '../../services/navigation'; -import { PrivilegesWrapper } from '../../lib/authorization'; +import { CapabilitiesWrapper } from '../../components/capabilities_wrapper'; import { Wizard } from '../create_transform/components/wizard'; import { overrideTransformForCloning } from '../../common/transform'; @@ -39,8 +38,6 @@ export const CloneTransformSection: FC = ({ match, location }) => { docTitleService.setTitle('createTransform'); }, []); - const api = useApi(); - const { esTransform } = useDocumentationLinks(); const transformId = match.params.transformId; @@ -50,52 +47,55 @@ export const CloneTransformSection: FC = ({ match, location }) => { const [isInitialized, setIsInitialized] = useState(false); const { error: searchItemsError, searchItems, setSavedObjectId } = useSearchItems(undefined); - const fetchTransformConfig = async () => { + useEffect(() => { + if (dataViewId === undefined) { + setErrorMessage( + i18n.translate('xpack.transform.clone.fetchErrorPromptText', { + defaultMessage: 'Could not fetch the Kibana data view ID.', + }) + ); + } else { + setSavedObjectId(dataViewId); + } + }, [dataViewId, setSavedObjectId]); + + useEffect(() => { if (searchItemsError !== undefined) { setTransformConfig(undefined); setErrorMessage(searchItemsError); setIsInitialized(true); - return; } + }, [searchItemsError]); + + const { data: transformConfigs, error } = useGetTransform( + transformId, + searchItemsError === undefined + ); - const transformConfigs = await api.getTransform(transformId); - if (isHttpFetchError(transformConfigs)) { + useEffect(() => { + if (error !== null && error.message !== errorMessage) { setTransformConfig(undefined); - setErrorMessage(transformConfigs.message); + setErrorMessage(error.message); setIsInitialized(true); return; } - try { - if (dataViewId === undefined) { - throw new Error( - i18n.translate('xpack.transform.clone.fetchErrorPromptText', { - defaultMessage: 'Could not fetch the Kibana data view ID.', - }) - ); - } - - setSavedObjectId(dataViewId); - - setTransformConfig(overrideTransformForCloning(transformConfigs.transforms[0])); - setErrorMessage(undefined); - setIsInitialized(true); - } catch (e) { - setTransformConfig(undefined); - if (e.message !== undefined) { - setErrorMessage(e.message); - } else { - setErrorMessage(JSON.stringify(e, null, 2)); + if (transformConfigs !== undefined) { + try { + setTransformConfig(overrideTransformForCloning(transformConfigs.transforms[0])); + setErrorMessage(undefined); + setIsInitialized(true); + } catch (e) { + setTransformConfig(undefined); + if (e.message !== undefined) { + setErrorMessage(e.message); + } else { + setErrorMessage(JSON.stringify(e, null, 2)); + } + setIsInitialized(true); } - setIsInitialized(true); } - }; - - useEffect(() => { - fetchTransformConfig(); - // The effect should only be called once. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [error, errorMessage, transformConfigs]); const docsLink = ( = ({ match, location }) => { ); return ( - + = ({ match, location }) => { )} - + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx index 900af603266b8..a125da52b0cae 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSwitch } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx index 43c6684a5a2bc..cb21e7026f27b 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiSwitch } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx index 2ee8bc9995df6..c53c5dae0b4ad 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SwitchModal } from './switch_modal'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx index ff08ab37bb3e6..37dd6dd01f98e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/switch_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx index db617690efc5f..8b76b8d7d7e1a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiButton, EuiButtonIcon, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx index 665c9986e6e69..4fea2f43bd5db 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/agg_label_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/agg_label_form.test.tsx.snap deleted file mode 100644 index 09056b8529f16..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/agg_label_form.test.tsx.snap +++ /dev/null @@ -1,72 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Date histogram aggregation 1`] = ` - - - - - the-group-by-agg-name - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_form.test.tsx.snap deleted file mode 100644 index 89b54e6d0a22f..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_form.test.tsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_summary.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_summary.test.tsx.snap deleted file mode 100644 index f08d7fab7c829..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/list_summary.test.tsx.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - -
- the-agg -
-
- -
-`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/popover_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/popover_form.test.tsx.snap deleted file mode 100644 index f7b4e836ee784..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/__snapshots__/popover_form.test.tsx.snap +++ /dev/null @@ -1,48 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Aggregation Minimal initialization 1`] = ` - - - - - - - Apply - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx index fc9f91f96b3ff..b036016314754 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/agg_label_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { AggName } from '../../../../../../common/types/aggregations'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -31,8 +31,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toBe('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx index 1525aa1a3320b..205fb93479339 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -29,8 +29,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toBe('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx index 71d728798c8fb..d1078f4d5a87a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/list_summary.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -26,8 +26,8 @@ describe('Transform: ', () => { list: { 'the-agg': item }, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toBe('the-agg'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx index 0587d4f0e1c46..88cdad407bd62 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { shallow } from 'enzyme'; import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import { AggName } from '../../../../../../common/types/aggregations'; @@ -29,7 +28,7 @@ describe('Transform: Aggregation ', () => { const otherAggNames: AggName[] = []; const onChange = (item: PivotAggsConfig) => {}; - const wrapper = shallow( + const { getByTestId } = render( ', () => { /> ); - expect(wrapper).toMatchSnapshot(); + const input = getByTestId('transformAggName'); + expect(input).toHaveValue('the-group-by-agg-name'); }); test('preserves the field for unsupported aggs', async () => { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx index a57d83b75aa10..2f5672d3ec592 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiSwitch } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_form.test.tsx.snap deleted file mode 100644 index 0bb11827655e0..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_form.test.tsx.snap +++ /dev/null @@ -1,234 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Date histogram aggregation 1`] = ` - - - - the-group-by-agg-name - - - - - 1m - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformIntervalFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - -`; - -exports[`Transform: Histogram aggregation 1`] = ` - - - - the-group-by-agg-name - - - - - 100 - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformIntervalFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - -`; - -exports[`Transform: Terms aggregation 1`] = ` - - - - the-group-by-agg-name - - - - - } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="transformIntervalFormPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_summary.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_summary.test.tsx.snap deleted file mode 100644 index b1d3afeff412c..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/group_by_label_summary.test.tsx.snap +++ /dev/null @@ -1,77 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Date histogram aggregation 1`] = ` - - - - the-options-data-id - - - - - 1m - - - -`; - -exports[`Transform: Histogram aggregation 1`] = ` - - - - the-options-data-id - - - - - 100 - - - -`; - -exports[`Transform: Terms aggregation 1`] = ` - - - - the-options-data-id - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_form.test.tsx.snap deleted file mode 100644 index da1e9a79680ad..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_form.test.tsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_summary.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_summary.test.tsx.snap deleted file mode 100644 index 724433a80d3a8..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/list_summary.test.tsx.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap deleted file mode 100644 index 9c9fb59eea4b1..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Group By Minimal initialization 1`] = ` - -`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx index 09ec34f90d751..2edcfc7a9bd2c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -29,9 +29,9 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); test('Histogram aggregation', () => { @@ -50,9 +50,9 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); test('Terms aggregation', () => { @@ -70,8 +70,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx index 689c27f5f81be..5c03d3bc8f320 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/group_by_label_summary.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -26,9 +26,9 @@ describe('Transform: ', () => { optionsDataId: 'the-options-data-id', }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); test('Histogram aggregation', () => { @@ -44,9 +44,9 @@ describe('Transform: ', () => { optionsDataId: 'the-options-data-id', }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); test('Terms aggregation', () => { @@ -61,8 +61,8 @@ describe('Transform: ', () => { optionsDataId: 'the-options-data-id', }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx index 61a11a8b551b3..eb942cad63b01 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -27,8 +27,8 @@ describe('Transform: ', () => { onChange() {}, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-group-by-agg-name'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx index 201b36a41b09c..eadddd8356e42 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/list_summary.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { PivotGroupByConfig, PIVOT_SUPPORTED_GROUP_BY_AGGS } from '../../../../common'; @@ -24,8 +24,8 @@ describe('Transform: ', () => { list: { 'the-options-data-id': item }, }; - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-options-data-id'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx index d5b1139e6cdae..4ef6cbbaf51be 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -101,7 +101,7 @@ describe('Transform: Group By ', () => { appName: 'the-test-app', }; - const wrapper = shallow( + const { getByDisplayValue } = render( ', () => { ); - expect(wrapper.find(PopoverForm)).toMatchSnapshot(); + expect(getByDisplayValue('the-agg-name')).toBeInTheDocument(); + expect(getByDisplayValue('1m')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx index e0a52978c0b4a..42b9d556f9dc2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiCode, EuiInputPopover } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx index 2e785c2d6680b..e2fa8912fc932 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { StepCreateForm, StepCreateFormProps } from './step_create_form'; @@ -16,6 +17,7 @@ jest.mock('../../../../app_dependencies'); describe('Transform: ', () => { test('Minimal initialization', () => { // Arrange + const queryClient = new QueryClient(); const props: StepCreateFormProps = { createDataView: false, transformId: 'the-transform-id', @@ -35,7 +37,11 @@ describe('Transform: ', () => { onChange() {}, }; - const { getByText } = render(); + const { getByText } = render( + + + + ); // Act // Assert diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index d61b18632cef4..3c78757a6f257 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -24,25 +24,19 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; import { DuplicateDataViewError } from '@kbn/data-plugin/public'; import type { RuntimeField } from '@kbn/data-views-plugin/common'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { PutTransformsResponseSchema } from '../../../../../../common/api_schemas/transforms'; -import { - isGetTransformsStatsResponseSchema, - isPutTransformsResponseSchema, - isStartTransformsResponseSchema, -} from '../../../../../../common/api_schemas/type_guards'; import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants'; import { getErrorMessage } from '../../../../../../common/utils/errors'; import { getTransformProgress } from '../../../../common'; -import { useApi } from '../../../../hooks/use_api'; +import { useCreateTransform, useGetTransformStats, useStartTransforms } from '../../../../hooks'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { RedirectToTransformManagement } from '../../../../common/navigation'; import { ToastNotificationText } from '../../../../components'; @@ -92,11 +86,10 @@ export const StepCreateForm: FC = React.memo( ); const [discoverLink, setDiscoverLink] = useState(); - const deps = useAppDependencies(); - const { share } = deps; - const dataViews = deps.data.dataViews; const toastNotifications = useToastNotifications(); - const isDiscoverAvailable = deps.application.capabilities.discover?.show ?? false; + const { application, data, i18n: i18nStart, share, theme } = useAppDependencies(); + const dataViews = data.dataViews; + const isDiscoverAvailable = application.capabilities.discover?.show ?? false; useEffect(() => { let unmounted = false; @@ -128,104 +121,38 @@ export const StepCreateForm: FC = React.memo( // eslint-disable-next-line react-hooks/exhaustive-deps }, [created, started, dataViewId]); - const { overlays, theme } = useAppDependencies(); - const api = useApi(); + const startTransforms = useStartTransforms(); + const createTransform = useCreateTransform(); - async function createTransform() { + function createTransformHandler(startAfterCreation = false) { setLoading(true); - const resp = await api.createTransform(transformId, transformConfig); - - if (!isPutTransformsResponseSchema(resp) || resp.errors.length > 0) { - let respErrors: - | PutTransformsResponseSchema['errors'] - | PutTransformsResponseSchema['errors'][number] - | undefined; - - if (isPutTransformsResponseSchema(resp) && resp.errors.length > 0) { - respErrors = resp.errors.length === 1 ? resp.errors[0] : resp.errors; + createTransform( + { transformId, transformConfig }, + { + onError: () => setCreated(false), + onSuccess: () => { + setCreated(true); + if (createDataView) { + createKibanaDataView(); + } + if (startAfterCreation) { + startTransform(); + } + }, + onSettled: () => setLoading(false), } - - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepCreateForm.createTransformErrorMessage', { - defaultMessage: 'An error occurred creating the transform {transformId}:', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - setCreated(false); - setLoading(false); - return false; - } - - toastNotifications.addSuccess( - i18n.translate('xpack.transform.stepCreateForm.createTransformSuccessMessage', { - defaultMessage: 'Request to create transform {transformId} acknowledged.', - values: { transformId }, - }) ); - setCreated(true); - setLoading(false); - - if (createDataView) { - createKibanaDataView(); - } - - return true; } - async function startTransform() { + function startTransform() { setLoading(true); - const resp = await api.startTransforms([{ id: transformId }]); - - if (isStartTransformsResponseSchema(resp) && resp[transformId]?.success === true) { - toastNotifications.addSuccess( - i18n.translate('xpack.transform.stepCreateForm.startTransformSuccessMessage', { - defaultMessage: 'Request to start transform {transformId} acknowledged.', - values: { transformId }, - }) - ); - setStarted(true); - setLoading(false); - return; - } - - const errorMessage = - isStartTransformsResponseSchema(resp) && resp[transformId]?.success === false - ? resp[transformId].error - : resp; - - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepCreateForm.startTransformErrorMessage', { - defaultMessage: 'An error occurred starting the transform {transformId}:', - values: { transformId }, - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), + startTransforms([{ id: transformId }], { + onError: () => setStarted(false), + onSuccess: (resp) => setStarted(resp[transformId]?.success === true), + onSettled: () => setLoading(false), }); - setStarted(false); - setLoading(false); - } - - async function createAndStartTransform() { - const acknowledged = await createTransform(); - if (acknowledged) { - await startTransform(); - } } const createKibanaDataView = async () => { @@ -250,13 +177,6 @@ export const StepCreateForm: FC = React.memo( true ); - toastNotifications.addSuccess( - i18n.translate('xpack.transform.stepCreateForm.createDataViewSuccessMessage', { - defaultMessage: 'Kibana data view {dataViewName} created successfully.', - values: { dataViewName }, - }) - ); - setDataViewId(newDataView.id); setLoading(false); return true; @@ -275,10 +195,10 @@ export const StepCreateForm: FC = React.memo( defaultMessage: 'An error occurred creating the Kibana data view {dataViewName}:', values: { dataViewName }, }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), }); setLoading(false); return false; @@ -288,57 +208,59 @@ export const StepCreateForm: FC = React.memo( const isBatchTransform = typeof transformConfig.sync === 'undefined'; - if ( - loading === false && - started === true && - progressPercentComplete === undefined && - isBatchTransform - ) { - // wrapping in function so we can keep the interval id in local scope - function startProgressBar() { - const interval = setInterval(async () => { - const stats = await api.getTransformStats(transformId); - - if ( - isGetTransformsStatsResponseSchema(stats) && - Array.isArray(stats.transforms) && - stats.transforms.length > 0 - ) { - const percent = - getTransformProgress({ - id: transformId, - config: { - ...transformConfig, - id: transformId, - }, - stats: stats.transforms[0], - }) || 0; - setProgressPercentComplete(percent); - if (percent >= 100) { - clearInterval(interval); - } - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepCreateForm.progressErrorMessage', { - defaultMessage: 'An error occurred getting the progress percentage:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - clearInterval(interval); - } - }, PROGRESS_REFRESH_INTERVAL_MS); + useEffect(() => { + if ( + loading === false && + started === true && + progressPercentComplete === undefined && + isBatchTransform + ) { setProgressPercentComplete(0); } + }, [loading, started, progressPercentComplete, isBatchTransform]); + + const progressBarRefetchEnabled = + isBatchTransform && + typeof progressPercentComplete === 'number' && + progressPercentComplete < 100; + const progressBarRefetchInterval = progressBarRefetchEnabled + ? PROGRESS_REFRESH_INTERVAL_MS + : false; + + const { data: stats } = useGetTransformStats( + transformId, + progressBarRefetchEnabled, + progressBarRefetchInterval + ); + + useEffect(() => { + if (stats === undefined) { + return; + } - startProgressBar(); - } + if (stats && Array.isArray(stats.transforms) && stats.transforms.length > 0) { + const percent = + getTransformProgress({ + id: transformId, + config: { + ...transformConfig, + id: transformId, + }, + stats: stats.transforms[0], + }) || 0; + setProgressPercentComplete(percent); + } else { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepCreateForm.progressErrorMessage', { + defaultMessage: 'An error occurred getting the progress percentage:', + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + }, [i18nStart, stats, theme, toastNotifications, transformConfig, transformId]); function getTransformConfigDevConsoleStatement() { return `PUT _transform/${transformId}\n${JSON.stringify(transformConfig, null, 2)}\n\n`; @@ -362,7 +284,7 @@ export const StepCreateForm: FC = React.memo( createTransformHandler(true)} data-test-subj="transformWizardCreateAndStartButton" > {i18n.translate('xpack.transform.stepCreateForm.createAndStartTransformButton', { @@ -436,7 +358,7 @@ export const StepCreateForm: FC = React.memo( createTransformHandler()} data-test-subj="transformWizardCreateButton" > {i18n.translate('xpack.transform.stepCreateForm.createTransformButton', { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx index b7704044fe0b3..2be067c777a1e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_summary.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; export const StepCreateSummary: FC = React.memo(() => { return null; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx index 0c6fae30fd1f0..c35cca74c72ac 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx @@ -6,7 +6,7 @@ */ import { debounce } from 'lodash'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -18,7 +18,6 @@ import { i18n } from '@kbn/i18n'; import { isMultiBucketAggregate } from '@kbn/ml-agg-utils'; import { useDataSearch } from '../../../../../../../hooks/use_data_search'; -import { isEsSearchResponseWithAggregations } from '../../../../../../../../../common/api_schemas/type_guards'; import { CreateTransformWizardContext } from '../../../../wizard/wizard'; import { useToastNotifications } from '../../../../../../../app_dependencies'; @@ -33,16 +32,22 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm selectedField, }) => { const { dataView, runtimeMappings } = useContext(CreateTransformWizardContext); - const dataSearch = useDataSearch(); const toastNotifications = useToastNotifications(); - const [options, setOptions] = useState([]); - const [isLoading, setIsLoading] = useState(true); const [searchValue, setSearchValue] = useState(''); + const debouncedOnSearchChange = useMemo( + () => debounce((d: string) => setSearchValue(d), 600), + [] + ); - const onSearchChange = (newSearchValue: string) => { - setSearchValue(newSearchValue); - }; + useEffect(() => { + // Simulate initial load. + debouncedOnSearchChange(''); + // Cancel debouncing when unmounting + return () => debouncedOnSearchChange.cancel(); + // Only call on mount + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, []); const updateConfig = useCallback( (update) => { @@ -56,80 +61,53 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm [config, onChange] ); - useEffect(() => { - const abortController = new AbortController(); - - const fetchOptions = debounce(async () => { - if (selectedField === undefined) return; - - setIsLoading(true); - setOptions([]); - - const esSearchRequest = { - index: dataView!.title, - body: { - ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), - query: { - wildcard: { - [selectedField!]: { - value: `*${searchValue}*`, - }, + const { data, isError, isLoading } = useDataSearch( + { + index: dataView!.title, + body: { + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), + query: { + wildcard: { + [selectedField!]: { + value: `*${searchValue}*`, }, }, - aggs: { - field_values: { - terms: { - field: selectedField, - size: 10, - }, + }, + aggs: { + field_values: { + terms: { + field: selectedField, + size: 10, }, }, - size: 0, }, - }; - - const response = await dataSearch(esSearchRequest, abortController.signal); - - setIsLoading(false); - - if ( - !( - isEsSearchResponseWithAggregations(response) && - isMultiBucketAggregate( - response.aggregations.field_values - ) - ) - ) { - toastNotifications.addWarning( - i18n.translate('xpack.transform.agg.popoverForm.filerAgg.term.errorFetchSuggestions', { - defaultMessage: 'Unable to fetch suggestions', - }) - ); - return; - } + size: 0, + }, + }, + // Check whether fetching should be enabled + selectedField !== undefined + ); - setOptions( - ( - response.aggregations.field_values - .buckets as estypes.AggregationsSignificantLongTermsBucket[] - ).map((value) => ({ label: value.key + '' })) + useEffect(() => { + if (isError) { + toastNotifications.addWarning( + i18n.translate('xpack.transform.agg.popoverForm.filerAgg.term.errorFetchSuggestions', { + defaultMessage: 'Unable to fetch suggestions', + }) ); - }, 600); - - fetchOptions(); - - return () => { - // make sure the ongoing request is canceled - fetchOptions.cancel(); - abortController.abort(); - }; + } /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [selectedField]); - - useEffect(() => { - // Simulate initial load. - onSearchChange(''); - }, []); + }, [isError]); + + const options: EuiComboBoxOptionOption[] = + isMultiBucketAggregate( + data?.aggregations?.field_values + ) + ? ( + data?.aggregations?.field_values + .buckets as estypes.AggregationsSignificantLongTermsBucket[] + ).map((value) => ({ label: value.key + '' })) + : []; useUpdateEffect(() => { // Reset value control on field change @@ -168,7 +146,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm onCreateOption={(value) => { updateConfig({ value }); }} - onSearchChange={onSearchChange} + onSearchChange={debouncedOnSearchChange} data-test-subj="transformFilterTermValueSelector" /> diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx index a83c7c7a5871c..ba0d9e93e2cce 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButtonIcon, EuiCallOut, EuiComboBox, EuiCopy, EuiFormRow } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx index efa28de596a18..861aeb778af74 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiButton, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index 6d5d6d0ea6fc9..3470cf5706a2e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { I18nProvider } from '@kbn/i18n-react'; import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; @@ -66,6 +67,7 @@ const createMockStorage = () => ({ describe('Transform: ', () => { test('Minimal initialization', async () => { // Arrange + const queryClient = new QueryClient(); const mlSharedImports = await getMlSharedImports(); const searchItems = { @@ -87,13 +89,15 @@ describe('Transform: ', () => { const { getByText } = render( - - - - - - - + + + + + + + + + ); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 81bdb47735a37..246460d11d3ee 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -78,6 +78,9 @@ const ALLOW_TIME_RANGE_ON_TRANSFORM_CONFIG = false; const advancedEditorsSidebarWidth = '220px'; +type PopulatedFields = Set; +const isPopulatedFields = (arg: unknown): arg is PopulatedFields => arg instanceof Set; + export const ConfigSectionTitle: FC<{ title: string }> = ({ title }) => ( <> @@ -132,7 +135,9 @@ export const StepDefineForm: FC = React.memo((props) => { transformConfigQuery, runtimeMappings, timeRangeMs, - fieldStatsContext?.populatedFields ?? null + isPopulatedFields(fieldStatsContext?.populatedFields) + ? [...fieldStatsContext.populatedFields] + : [] ), dataTestSubj: 'transformIndexPreview', toastNotifications, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx index 85cef1fb2958e..4f378d4394da0 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -30,6 +31,7 @@ describe('Transform: ', () => { // Using the async/await wait()/done() pattern to avoid act() errors. test('Minimal initialization', async () => { // Arrange + const queryClient = new QueryClient(); const mlSharedImports = await getMlSharedImports(); const searchItems = { @@ -78,9 +80,11 @@ describe('Transform: ', () => { }; const { queryByText } = render( - - - + + + + + ); // Act diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx index 3aec137a3adf8..441e18ebb43d2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/transform_function_selector.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; import { TRANSFORM_FUNCTION, TransformFunction } from '../../../../../../common/constants'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 4e8104a7645ee..da9ebbf9b2ef7 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -25,15 +25,9 @@ import { } from '@elastic/eui'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; -import { isHttpFetchError } from '@kbn/core-http-browser'; import { retentionPolicyMaxAgeInvalidErrorMessage } from '../../../../common/constants/validation_messages'; -import { - isEsIndices, - isEsIngestPipelines, - isPostTransformsPreviewResponseSchema, -} from '../../../../../../common/api_schemas/type_guards'; import { DEFAULT_TRANSFORM_FREQUENCY } from '../../../../../../common/constants'; import { TransformId } from '../../../../../../common/types/transform'; import { isValidIndexName } from '../../../../../../common/utils/es_utils'; @@ -42,16 +36,22 @@ import { getErrorMessage } from '../../../../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { ToastNotificationText } from '../../../../components'; -import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; +import { + useDocumentationLinks, + useGetDataViewTitles, + useGetEsIndices, + useGetEsIngestPipelines, + useGetTransforms, + useGetTransformsPreview, +} from '../../../../hooks'; import { SearchItems } from '../../../../hooks/use_search_items'; -import { useApi } from '../../../../hooks/use_api'; import { StepDetailsTimeField } from './step_details_time_field'; import { getTransformConfigQuery, getPreviewTransformRequestBody, isTransformIdValid, } from '../../../../common'; -import { EsIndexName, DataViewTitle } from './common'; +import { EsIndexName } from './common'; import { continuousModeDelayValidator, integerRangeMinus1To100Validator, @@ -73,8 +73,8 @@ interface StepDetailsFormProps { export const StepDetailsForm: FC = React.memo( ({ overrides = {}, onChange, searchItems, stepDefineState }) => { - const deps = useAppDependencies(); - const { capabilities } = deps.application; + const { application, i18n: i18nStart, theme } = useAppDependencies(); + const { capabilities } = application; const toastNotifications = useToastNotifications(); const { esIndicesCreateIndex } = useDocumentationLinks(); @@ -90,19 +90,15 @@ export const StepDetailsForm: FC = React.memo( const [destinationIngestPipeline, setDestinationIngestPipeline] = useState( defaults.destinationIngestPipeline ); - const [transformIds, setTransformIds] = useState([]); - const [indexNames, setIndexNames] = useState([]); - const [ingestPipelineNames, setIngestPipelineNames] = useState([]); const canCreateDataView = useMemo( () => - capabilities.savedObjectsManagement.edit === true || - capabilities.indexPatterns.save === true, + capabilities.savedObjectsManagement?.edit === true || + capabilities.indexPatterns?.save === true, [capabilities] ); // Index pattern state - const [dataViewTitles, setDataViewTitles] = useState([]); const [createDataView, setCreateDataView] = useState( canCreateDataView === false ? false : defaults.createDataView ); @@ -125,126 +121,122 @@ export const StepDetailsForm: FC = React.memo( [setDataViewTimeField, dataViewAvailableTimeFields] ); - const { overlays, theme } = useAppDependencies(); - const api = useApi(); + const { + error: transformsError, + data: { transformIds }, + } = useGetTransforms(); + + useEffect(() => { + if (transformsError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { + defaultMessage: 'An error occurred getting the existing transform IDs:', + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [transformsError]); + + const previewRequest = useMemo(() => { + const { searchQuery, previewRequest: partialPreviewRequest } = stepDefineState; + const transformConfigQuery = getTransformConfigQuery(searchQuery); + return getPreviewTransformRequestBody( + searchItems.dataView, + transformConfigQuery, + partialPreviewRequest, + stepDefineState.runtimeMappings + ); + }, [searchItems.dataView, stepDefineState]); + const { error: transformsPreviewError, data: transformPreview } = + useGetTransformsPreview(previewRequest); - // fetch existing transform IDs and indices once for form validation useEffect(() => { - // use an IIFE to avoid returning a Promise to useEffect. - (async function () { - const { searchQuery, previewRequest: partialPreviewRequest } = stepDefineState; - const transformConfigQuery = getTransformConfigQuery(searchQuery); - const previewRequest = getPreviewTransformRequestBody( - searchItems.dataView, - transformConfigQuery, - partialPreviewRequest, - stepDefineState.runtimeMappings + if (transformPreview) { + const properties = transformPreview.generated_dest_index.mappings.properties; + const timeFields: string[] = Object.keys(properties).filter( + (col) => properties[col].type === 'date' ); - const transformPreview = await api.getTransformsPreview(previewRequest); - - if (isPostTransformsPreviewResponseSchema(transformPreview)) { - const properties = transformPreview.generated_dest_index.mappings.properties; - const timeFields: string[] = Object.keys(properties).filter( - (col) => properties[col].type === 'date' - ); - - setDataViewAvailableTimeFields(timeFields); - setDataViewTimeField(timeFields[0]); - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformPreview', { - defaultMessage: 'An error occurred fetching the transform preview', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } + setDataViewAvailableTimeFields(timeFields); + setDataViewTimeField(timeFields[0]); + } + }, [transformPreview]); - const resp = await api.getTransforms(); - - if (isHttpFetchError(resp)) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { - defaultMessage: 'An error occurred getting the existing transform IDs:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } else { - setTransformIds(resp.transforms.map((transform) => transform.id)); - } + useEffect(() => { + if (transformsPreviewError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformPreview', { + defaultMessage: 'An error occurred fetching the transform preview', + }), + text: toMountPoint( + , + { theme, i18n: i18nStart } + ), + }); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [transformsPreviewError]); - const [indices, ingestPipelines] = await Promise.all([ - api.getEsIndices(), - api.getEsIngestPipelines(), - ]); - - if (isEsIndices(indices)) { - setIndexNames(indices.map((index) => index.name)); - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { - defaultMessage: 'An error occurred getting the existing index names:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } + const { error: esIndicesError, data: esIndicesData } = useGetEsIndices(); + const indexNames = esIndicesData?.map((index) => index.name) ?? []; - if (isEsIngestPipelines(ingestPipelines)) { - setIngestPipelineNames(ingestPipelines.map(({ name }) => name)); - } else { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIngestPipelines', { - defaultMessage: 'An error occurred getting the existing ingest pipeline names:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } + useEffect(() => { + if (esIndicesError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { + defaultMessage: 'An error occurred getting the existing index names:', + }), + text: toMountPoint(, { + theme, + i18n: i18nStart, + }), + }); + } + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ + }, [esIndicesError]); - try { - setDataViewTitles(await deps.data.dataViews.getTitles()); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingDataViewTitles', { - defaultMessage: 'An error occurred getting the existing data view titles:', - }), - text: toMountPoint( - , - { theme$: theme.theme$ } - ), - }); - } - })(); - // run once - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { error: esIngestPipelinesError, data: esIngestPipelinesData } = + useGetEsIngestPipelines(); + const ingestPipelineNames = esIngestPipelinesData?.map(({ name }) => name) ?? []; + + useEffect(() => { + if (esIngestPipelinesError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIngestPipelines', { + defaultMessage: 'An error occurred getting the existing ingest pipeline names:', + }), + text: toMountPoint( + , + { theme, i18n: i18nStart } + ), + }); + } + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ + }, [esIngestPipelinesError]); + + const { error: dataViewTitlesError, data: dataViewTitles } = useGetDataViewTitles(); + + useEffect(() => { + if (dataViewTitlesError !== null) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingDataViewTitles', { + defaultMessage: 'An error occurred getting the existing data view titles:', + }), + text: toMountPoint( + , + { theme, i18n: i18nStart } + ), + }); + } + }, [dataViewTitlesError]); const dateFieldNames = searchItems.dataView.fields .filter((f) => f.type === KBN_FIELD_TYPES.DATE) @@ -284,7 +276,6 @@ export const StepDetailsForm: FC = React.memo( ); setRetentionPolicyMaxAge(''); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isRetentionPolicyEnabled]); const transformIdExists = transformIds.some((id) => transformId === id); @@ -294,7 +285,7 @@ export const StepDetailsForm: FC = React.memo( const indexNameExists = indexNames.some((name) => destinationIndex === name); const indexNameEmpty = destinationIndex === ''; const indexNameValid = isValidIndexName(destinationIndex); - const dataViewTitleExists = dataViewTitles.some((name) => destinationIndex === name); + const dataViewTitleExists = dataViewTitles?.some((name) => destinationIndex === name) ?? false; const [transformFrequency, setTransformFrequency] = useState(defaults.transformFrequency); const isTransformFrequencyValid = transformFrequencyValidator(transformFrequency); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx index 80203af34e105..b3b11a6e4764e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx index d750bf6c7e1fd..76ce803bdb415 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx index 2d2a5b1fcad93..cbc53383541ed 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiConfirmModal } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx index 103b8378cef7f..2ffb8437d4861 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard_nav/wizard_nav.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx index e8780ba180e8d..6ee4d5c812f73 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx @@ -8,12 +8,14 @@ import React, { FC, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; + import { EuiButtonEmpty, EuiCallOut, EuiPageTemplate, EuiSpacer } from '@elastic/eui'; -import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants'; + import { useDocumentationLinks } from '../../hooks/use_documentation_links'; import { useSearchItems } from '../../hooks/use_search_items'; -import { BREADCRUMB_SECTION, breadcrumbService, docTitleService } from '../../services/navigation'; -import { PrivilegesWrapper } from '../../lib/authorization'; +import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; +import { CapabilitiesWrapper } from '../../components/capabilities_wrapper'; + import { Wizard } from './components/wizard'; type Props = RouteComponentProps<{ savedObjectId: string }>; @@ -43,7 +45,14 @@ export const CreateTransformSection: FC = ({ match }) => { ); return ( - + = ({ match }) => { )} {searchItems !== undefined && } - + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/__snapshots__/transform_management_section.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/__snapshots__/transform_management_section.test.tsx.snap deleted file mode 100644 index 8348f93b32140..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/__snapshots__/transform_management_section.test.tsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx index e6f3eeb4a9064..21c33361e398b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx @@ -5,11 +5,12 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; export const cloneActionNameText = i18n.translate( 'xpack.transform.transformList.cloneActionNameText', diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx index 35d5e9f05c315..d78211e19bdb4 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { AuthorizationContext } from '../../../../lib/authorization'; import { TransformListAction, TransformListRow } from '../../../../common'; import { SECTION_SLUG } from '../../../../common/constants'; -import { useSearchItems } from '../../../../hooks/use_search_items'; +import { useTransformCapabilities, useSearchItems } from '../../../../hooks'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { cloneActionNameText, CloneActionName } from './clone_action_name'; @@ -26,7 +25,7 @@ export const useCloneAction = (forceDisable: boolean, transformNodes: number) => const { getDataViewIdByTitle, loadDataViews } = useSearchItems(undefined); - const { canCreateTransform } = useContext(AuthorizationContext).capabilities; + const { canCreateTransform } = useTransformCapabilities(); const clickHandler = useCallback( async (item: TransformListRow) => { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx index c8d67a86d579a..fec35288d80b5 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/create_alert_rule_action_name.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; + +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; interface CreateAlertRuleActionProps { disabled: boolean; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx index 070f1eb08ac60..af75222fa85e7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_create_alert/use_create_alert_rule_action.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo } from 'react'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import React, { useCallback, useMemo } from 'react'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListAction, TransformListRow } from '../../../../common'; import { crateAlertRuleActionNameText, @@ -17,7 +17,7 @@ import { isContinuousTransform } from '../../../../../../common/types/transform' export type CreateAlertRuleAction = ReturnType; export const useCreateAlertRuleAction = (forceDisable: boolean) => { - const { canCreateTransformAlerts } = useContext(AuthorizationContext).capabilities; + const { canCreateTransformAlerts } = useTransformCapabilities(); const { setCreateAlertRule } = useAlertRuleFlyout(); const clickHandler = useCallback( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap deleted file mode 100644 index fe1e813ca9843..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - Delete - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx index 1fb0d69682cda..37daeeb75e138 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { DeleteActionName, DeleteActionNameProps } from './delete_action_name'; @@ -21,7 +21,7 @@ describe('Transform: Transform List Actions ', () => { isBulkAction: false, }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { container } = render(); + expect(container.textContent).toBe('Delete'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx index fa1142938efe7..93e346e6c7a1d 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx @@ -5,11 +5,15 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; + import { EuiToolTip } from '@elastic/eui'; + import { TransformState, TRANSFORM_STATE } from '../../../../../../common/constants'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + import { TransformListRow } from '../../../../common'; export const deleteActionNameText = i18n.translate( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx index 357809b54746b..5996e271604e3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx @@ -5,13 +5,16 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useDeleteIndexAndTargetIndex, useDeleteTransforms } from '../../../../hooks'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { + useDeleteIndexAndTargetIndex, + useDeleteTransforms, + useTransformCapabilities, +} from '../../../../hooks'; import { deleteActionNameText, @@ -21,7 +24,7 @@ import { export type DeleteAction = ReturnType; export const useDeleteAction = (forceDisable: boolean) => { - const { canDeleteTransform } = useContext(AuthorizationContext).capabilities; + const { canDeleteTransform } = useTransformCapabilities(); const deleteTransforms = useDeleteTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx index f7cc72c2236b0..c3d359e648c28 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx index 3bf5ee8e611f6..7fc16ebfff278 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx @@ -5,16 +5,15 @@ * 2.0. */ -import React, { useContext, FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + +import { useTransformCapabilities } from '../../../../hooks'; export const editActionNameText = i18n.translate( 'xpack.transform.transformList.editActionNameText', @@ -24,7 +23,7 @@ export const editActionNameText = i18n.translate( ); export const EditActionName: FC = () => { - const { canCreateTransform } = useContext(AuthorizationContext).capabilities; + const { canCreateTransform } = useTransformCapabilities(); if (!canCreateTransform) { return ( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx index 19d60b8b64381..40e7334197b0c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { editActionNameText, EditActionName } from './edit_action_name'; import { useSearchItems } from '../../../../hooks/use_search_items'; @@ -19,7 +19,7 @@ import { TransformConfigUnion } from '../../../../../../common/types/transform'; export type EditAction = ReturnType; export const useEditAction = (forceDisable: boolean, transformNodes: number) => { - const { canCreateTransform } = useContext(AuthorizationContext).capabilities; + const { canCreateTransform } = useTransformCapabilities(); const [config, setConfig] = useState(); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx index 9e848fdd8323e..edfde117abbd7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal } from '@elastic/eui'; import type { ReauthorizeAction } from './use_reauthorize_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx index e07ae03ec46b0..ee883a3e7e77b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx @@ -5,14 +5,16 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { type FC } from 'react'; + import { EuiToolTip, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + import { needsReauthorization } from '../../../../common/reauthorization_utils'; -import { - AuthorizationContext, - createCapabilityFailureMessage, -} from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListRow } from '../../../../common'; export const reauthorizeActionNameText = i18n.translate( @@ -45,7 +47,7 @@ export const ReauthorizeActionName: FC = ({ forceDisable, transformNodes, }) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); // Disable start for batch transforms which have completed. const someNeedsReauthorization = items.some(needsReauthorization); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx index 086f5451d53be..67e618765e42e 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { sortTransformsToReauthorize } from './sort_transforms_to_reauthorize'; import { needsReauthorization } from '../../../../common/reauthorization_utils'; @@ -17,11 +17,11 @@ import { } from './reauthorize_action_name'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; export type ReauthorizeAction = ReturnType; export const useReauthorizeAction = (forceDisable: boolean, transformNodes: number) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const reauthorizeTransforms = useReauthorizeTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx index c6ea0f5f7270d..6d5f56d3e7297 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx @@ -5,11 +5,15 @@ * 2.0. */ -import React, { FC } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { type FC } from 'react'; + import { EuiToolTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + import { TransformState, TRANSFORM_STATE } from '../../../../../../common/constants'; -import { createCapabilityFailureMessage } from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + import { TransformListRow } from '../../../../common'; export const resetActionNameText = i18n.translate( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx index 70164bc22a63c..23a399fdb9086 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx @@ -5,19 +5,18 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useResetTransforms } from '../../../../hooks'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities, useResetTransforms } from '../../../../hooks'; import { resetActionNameText, isResetActionDisabled, ResetActionName } from './reset_action_name'; export type ResetAction = ReturnType; export const useResetAction = (forceDisable: boolean) => { - const { canResetTransform } = useContext(AuthorizationContext).capabilities; + const { canResetTransform } = useTransformCapabilities(); const resetTransforms = useResetTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx index 71b6a055c0d94..0c3be1cdad70b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/schedule_now_action_name.tsx @@ -5,14 +5,15 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { type FC } from 'react'; + import { EuiToolTip } from '@elastic/eui'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { i18n } from '@kbn/i18n'; + +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListRow, isCompletedBatchTransform } from '../../../../common'; export const scheduleNowActionNameText = i18n.translate( @@ -48,7 +49,7 @@ export const ScheduleNowActionName: FC = ({ forceDisable, transformNodes, }) => { - const { canScheduleNowTransform } = useContext(AuthorizationContext).capabilities; + const { canScheduleNowTransform } = useTransformCapabilities(); const isBulkAction = items.length > 1; // Disable schedule-now for batch transforms which have completed. diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx index dda70deb225a6..a13d3da89f677 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_schedule_now/use_schedule_now_action.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import React, { useContext, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListAction, TransformListRow } from '../../../../common'; import { useScheduleNowTransforms } from '../../../../hooks'; @@ -21,8 +21,7 @@ import { export type ScheduleNowAction = ReturnType; export const useScheduleNowAction = (forceDisable: boolean, transformNodes: number) => { - const { canScheduleNowTransform } = useContext(AuthorizationContext).capabilities; - + const { canScheduleNowTransform } = useTransformCapabilities(); const scheduleNowTransforms = useScheduleNowTransforms(); const action: TransformListAction = useMemo( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap deleted file mode 100644 index 20b0691b55bf9..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - Start - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx index 7ad6897034e10..ab98f36222957 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { StartAction } from './use_start_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx index ba4619b022620..6ef0a995186c7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { TransformListRow } from '../../../../common'; import { StartActionName, StartActionNameProps } from './start_action_name'; @@ -16,6 +17,8 @@ import transformListRow from '../../../../common/__mocks__/transform_list_row.js jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); +const queryClient = new QueryClient(); + describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { // @ts-expect-error mock data is too loosely typed @@ -26,8 +29,11 @@ describe('Transform: Transform List Actions ', () => { transformNodes: 1, }; - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render( + + + + ); + expect(container.textContent).toBe('Start'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx index 844d9755d7073..c50c83d25edc5 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx @@ -5,16 +5,14 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { TransformListRow, isCompletedBatchTransform } from '../../../../common'; export const startActionNameText = i18n.translate( @@ -55,7 +53,7 @@ export const StartActionName: FC = ({ forceDisable, transformNodes, }) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const isBulkAction = items.length > 1; // Disable start for batch transforms which have completed. diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx index 20910cf5fa0a5..168174730b706 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx @@ -5,19 +5,18 @@ * 2.0. */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; -import { AuthorizationContext } from '../../../../lib/authorization'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useStartTransforms } from '../../../../hooks'; +import { useTransformCapabilities, useStartTransforms } from '../../../../hooks'; import { isStartActionDisabled, startActionNameText, StartActionName } from './start_action_name'; export type StartAction = ReturnType; export const useStartAction = (forceDisable: boolean, transformNodes: number) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const startTransforms = useStartTransforms(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap deleted file mode 100644 index fd97412fa1875..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - Stop - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx index 9496dbd82c70d..e32bf043a2221 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { TransformListRow } from '../../../../common'; import { StopActionName, StopActionNameProps } from './stop_action_name'; @@ -16,6 +17,8 @@ import transformListRow from '../../../../common/__mocks__/transform_list_row.js jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); +const queryClient = new QueryClient(); + describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { // @ts-expect-error mock data is too loosely typed @@ -25,8 +28,11 @@ describe('Transform: Transform List Actions ', () => { items: [item], }; - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render( + + + + ); + expect(container.textContent).toBe('Stop'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx index 1c729555f7df3..e5bc1425cdd99 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx @@ -5,17 +5,15 @@ * 2.0. */ -import React, { FC, useContext } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip } from '@elastic/eui'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; import { TransformListRow } from '../../../../common'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; export const stopActionNameText = i18n.translate( 'xpack.transform.transformList.stopActionNameText', @@ -43,7 +41,7 @@ export interface StopActionNameProps { } export const StopActionName: FC = ({ items, forceDisable }) => { const isBulkAction = items.length > 1; - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); // Disable stop action if one of the transforms is stopped already const stoppedTransform = items.some( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx index ac53ee83f6f65..e410704341177 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx @@ -5,32 +5,28 @@ * 2.0. */ -import React, { useCallback, useContext, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { TRANSFORM_STATE } from '../../../../../../common/constants'; -import { AuthorizationContext } from '../../../../lib/authorization'; import { TransformListAction, TransformListRow } from '../../../../common'; -import { useStopTransforms } from '../../../../hooks'; +import { useTransformCapabilities, useStopTransforms } from '../../../../hooks'; import { isStopActionDisabled, stopActionNameText, StopActionName } from './stop_action_name'; import { isManagedTransform } from '../../../../common/managed_transforms_utils'; export type StopAction = ReturnType; export const useStopAction = (forceDisable: boolean) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; - + const { canStartStopTransform } = useTransformCapabilities(); const stopTransforms = useStopTransforms(); const [isModalVisible, setModalVisible] = useState(false); const [items, setItems] = useState([]); const closeModal = () => setModalVisible(false); - const openModal = (newItems: TransformListRow[]) => { if (Array.isArray(newItems)) { setItems(newItems); setModalVisible(true); } }; - const stopAndCloseModal = useCallback( (transformSelection: TransformListRow[]) => { setModalVisible(false); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap deleted file mode 100644 index a529ef04f9230..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/__snapshots__/create_transform_button.test.tsx.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Minimal initialization 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx index 0a7324fd09ffc..da84ef2faa8e3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx @@ -5,17 +5,23 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { CreateTransformButton } from './create_transform_button'; jest.mock('../../../../../shared_imports'); +const queryClient = new QueryClient(); + describe('Transform: Transform List ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render( + + + + ); + expect(container.textContent).toBe('Create a transform'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx index 14697f9af4080..9c1fa4be8c101 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.tsx @@ -5,16 +5,15 @@ * 2.0. */ -import React, { useContext, FC, MouseEventHandler } from 'react'; +import React, { type FC, type MouseEventHandler } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; +import { createCapabilityFailureMessage } from '../../../../../../common/utils/create_capability_failure_message'; + +import { useTransformCapabilities } from '../../../../hooks'; interface CreateTransformButtonProps { onClick: MouseEventHandler; @@ -25,7 +24,7 @@ export const CreateTransformButton: FC = ({ onClick, transformNodes, }) => { - const { capabilities } = useContext(AuthorizationContext); + const capabilities = useTransformCapabilities(); const disabled = !capabilities.canCreateTransform || diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx index 75f333960fa54..cdaabb3a3b200 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_callout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx index a337d73ca54b3..d9310762ef3e0 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiFieldText, EuiFormRow } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx index b5bb7f3fb258f..519bdc94011e1 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx @@ -5,15 +5,13 @@ * 2.0. */ -import React, { useEffect, useState, type FC } from 'react'; +import React, { type FC } from 'react'; import { useEuiTheme, EuiComboBox, EuiFormRow, EuiSkeletonRectangle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEsIngestPipelines } from '../../../../../../common/api_schemas/type_guards'; - -import { useApi } from '../../../../hooks/use_api'; +import { useGetEsIngestPipelines } from '../../../../hooks'; import { EditTransformFlyoutFormTextInput } from './edit_transform_flyout_form_text_input'; import { useEditTransformFlyout } from './use_edit_transform_flyout'; @@ -30,35 +28,8 @@ export const EditTransformIngestPipeline: FC = () => { const { errorMessages, value } = useEditTransformFlyout('destinationIngestPipeline'); const { formField } = useEditTransformFlyout('actions'); - const api = useApi(); - - const [ingestPipelineNames, setIngestPipelineNames] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - useEffect(function fetchPipelinesOnMount() { - let unmounted = false; - - async function getIngestPipelineNames() { - try { - const ingestPipelines = await api.getEsIngestPipelines(); - - if (!unmounted && isEsIngestPipelines(ingestPipelines)) { - setIngestPipelineNames(ingestPipelines.map(({ name }) => name)); - } - } finally { - if (!unmounted) { - setIsLoading(false); - } - } - } - - getIngestPipelineNames(); - - return () => { - unmounted = true; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { data: esIngestPipelinesData, isLoading } = useGetEsIngestPipelines(); + const ingestPipelineNames = esIngestPipelinesData?.map(({ name }) => name) ?? []; return ( <> diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx index 06e30d584de46..b55b6f90a0aa3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx @@ -11,12 +11,9 @@ import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isPostTransformsUpdateResponseSchema } from '../../../../../../common/api_schemas/type_guards'; import { getErrorMessage } from '../../../../../../common/utils/errors'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common'; -import { useToastNotifications } from '../../../../app_dependencies'; -import { useApi } from '../../../../hooks/use_api'; +import { useUpdateTransform } from '../../../../hooks'; import { useEditTransformFlyout } from './use_edit_transform_flyout'; @@ -25,33 +22,20 @@ interface EditTransformUpdateButtonProps { } export const EditTransformUpdateButton: FC = ({ closeFlyout }) => { - const api = useApi(); - const toastNotifications = useToastNotifications(); - const requestConfig = useEditTransformFlyout('requestConfig'); const isUpdateButtonDisabled = useEditTransformFlyout('isUpdateButtonDisabled'); const config = useEditTransformFlyout('config'); const { apiError } = useEditTransformFlyout('actions'); + const updateTransfrom = useUpdateTransform(config.id, requestConfig); + async function submitFormHandler() { apiError(undefined); - const transformId = config.id; - - const resp = await api.updateTransform(transformId, requestConfig); - - if (!isPostTransformsUpdateResponseSchema(resp)) { - apiError(getErrorMessage(resp)); - return; - } - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.editTransformSuccessMessage', { - defaultMessage: 'Transform {transformId} updated.', - values: { transformId }, - }) - ); - closeFlyout(); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); + updateTransfrom(undefined, { + onError: (error) => apiError(getErrorMessage(error)), + onSuccess: () => closeFlyout(), + }); } return ( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx index d0ea13ec11df1..efd4fb1d500af 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/refresh_transform_list_button/refresh_transform_list_button.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx index 5647060ee7a72..3f2bd7fb70c0c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx @@ -8,7 +8,8 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { useAppDependencies } from '../../../../app_dependencies'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx index 810d60f3b8fd7..cfd0d112ba90b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stat.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; export interface StatsBarStat { label: string; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx index 21674ae24301c..849c651b37ea0 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/stats_bar/stats_bar.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; + import { Stat, StatsBarStat } from './stat'; interface Stats { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_details_pane.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_details_pane.test.tsx.snap deleted file mode 100644 index 39964399f66db..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_details_pane.test.tsx.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Job List Expanded Row Minimal initialization 1`] = ` -
- - - -
- - - -
-`; - -exports[`Transform: Job List Expanded Row
Minimal initialization 1`] = ` - - - - the-section-title - - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap deleted file mode 100644 index 19a5055954d84..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Expanded Row Minimal initialization 1`] = ` -
- - - - - { - "id": "fq_date_histogram_1m_1441", - "source": { - "index": [ - "farequote-2019" - ], - "query": { - "match_all": {} - } - }, - "dest": { - "index": "fq_date_histogram_1m_1441" - }, - "pivot": { - "group_by": { - "@timestamp": { - "date_histogram": { - "field": "@timestamp", - "calendar_interval": "1m" - } - } - }, - "aggregations": { - "responsetime.avg": { - "avg": { - "field": "responsetime" - } - } - } - }, - "version": "8.0.0", - "create_time": 1564388146667 -} - - - -   - - -
-`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap deleted file mode 100644 index 77df5dc76f68a..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Minimal initialization 1`] = ` -<_EuiPageEmptyPrompt - actions={ - Array [ - - Create your first transform - , - ] - } - color="subdued" - data-test-subj="transformNoTransformsFound" - title={ -

- No transforms found -

- } -/> -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx index a4c8a497d2202..ad375d47ce4cc 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { ExpandedRowDetailsPane, Section, SectionConfig } from './expanded_row_details_pane'; @@ -23,16 +23,20 @@ const section: SectionConfig = { describe('Transform: Job List Expanded Row ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); + const { container } = render(); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-section-title'); + expect(container.textContent).toContain('the-item-title'); + expect(container.textContent).toContain('the-item-description'); }); }); describe('Transform: Job List Expanded Row
', () => { test('Minimal initialization', () => { - const wrapper = shallow(
); + const { container } = render(
); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('the-section-title'); + expect(container.textContent).toContain('the-item-title'); + expect(container.textContent).toContain('the-item-description'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx index 7b1a5de79fae5..ff69aab3aa3eb 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; @@ -14,8 +14,7 @@ import { ExpandedRowJsonPane } from './expanded_row_json_pane'; describe('Transform: Transform List Expanded Row ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); + const { container } = render(); + expect(container.textContent).toContain(JSON.stringify(transformListRow.config, null, 2)); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx index c68a57ea12109..48e17d9157226 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx index 4cf387ad2973b..2ec73e4485dd1 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx @@ -15,17 +15,15 @@ import { EuiToolTip, EuiButtonIcon, } from '@elastic/eui'; -import { euiLightVars as theme } from '@kbn/ui-theme'; +import { euiLightVars as theme } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; import { DEFAULT_MAX_AUDIT_MESSAGE_SIZE, TIME_FORMAT } from '../../../../../../common/constants'; -import { isGetTransformsAuditMessagesResponseSchema } from '../../../../../../common/api_schemas/type_guards'; import { TransformMessage } from '../../../../../../common/types/messages'; -import { useApi } from '../../../../hooks/use_api'; import { JobIcon } from '../../../../components/job_icon'; -import { useRefreshTransformList } from '../../../../common'; +import { useGetTransformAuditMessages, useRefreshTransformList } from '../../../../hooks'; interface ExpandedRowMessagesPaneProps { transformId: string; @@ -37,12 +35,6 @@ interface Sorting { } export const ExpandedRowMessagesPane: FC = ({ transformId }) => { - const [messages, setMessages] = useState([]); - const [msgCount, setMsgCount] = useState(0); - - const [isLoading, setIsLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [sorting, setSorting] = useState<{ sort: Sorting }>({ @@ -52,58 +44,24 @@ export const ExpandedRowMessagesPane: FC = ({ tran }, }); - const api = useApi(); - - const getMessagesFactory = ( - sortField: keyof TransformMessage = 'timestamp', - sortDirection: 'asc' | 'desc' = 'desc' - ) => { - let concurrentLoads = 0; - - return async function getMessages() { - concurrentLoads++; - - if (concurrentLoads > 1) { - return; - } - - setIsLoading(true); - const messagesResp = await api.getTransformAuditMessages( - transformId, - sortField, - sortDirection - ); - - if (!isGetTransformsAuditMessagesResponseSchema(messagesResp)) { - setIsLoading(false); - setErrorMessage( - i18n.translate( - 'xpack.transform.transformList.transformDetails.messagesPane.errorMessage', - { - defaultMessage: 'Messages could not be loaded', - } - ) - ); - return; - } - - setIsLoading(false); - setMessages(messagesResp.messages); - setMsgCount(messagesResp.total); - - concurrentLoads--; - - if (concurrentLoads > 0) { - concurrentLoads = 0; - getMessages(); - } - }; - }; - const { refresh: refreshMessage } = useRefreshTransformList({ onRefresh: getMessagesFactory() }); + const { + isLoading, + error, + data = { messages: [], total: 0 }, + } = useGetTransformAuditMessages(transformId, sorting.sort.field, sorting.sort.direction); + const { messages, total } = data; + const errorMessage = + error !== null + ? i18n.translate('xpack.transform.transformList.transformDetails.messagesPane.errorMessage', { + defaultMessage: 'Messages could not be loaded', + }) + : ''; + + const refreshTransformList = useRefreshTransformList(); const columns = [ { - name: refreshMessage ? ( + name: refreshTransformList ? ( = ({ tran // TODO: Replace this with ML's blurButtonOnClick when it's moved to a shared package onClick={(e: MouseEvent) => { (e.currentTarget as HTMLButtonElement).blur(); - refreshMessage(); + refreshTransformList(); }} iconType="refresh" aria-label={i18n.translate('xpack.transform.transformList.refreshAriaLabel', { @@ -162,7 +120,7 @@ export const ExpandedRowMessagesPane: FC = ({ tran const getPageOfMessages = ({ index, size }: { index: number; size: number }) => { let list = messages; - if (msgCount <= DEFAULT_MAX_AUDIT_MESSAGE_SIZE) { + if (total <= DEFAULT_MAX_AUDIT_MESSAGE_SIZE) { const sortField = sorting.sort.field ?? 'timestamp'; list = messages.sort((a: TransformMessage, b: TransformMessage) => { const prev = a[sortField] as any; @@ -192,12 +150,6 @@ export const ExpandedRowMessagesPane: FC = ({ tran setPageSize(size); if (sort) { setSorting({ sort }); - - // Since we only show 500 messages, if user wants oldest messages first - // we need to make sure we fetch them from elasticsearch - if (msgCount > DEFAULT_MAX_AUDIT_MESSAGE_SIZE) { - getMessagesFactory(sort.field, sort.direction)(); - } } }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx index 430fa9478f60a..fc4bf0e24d14d 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx @@ -5,25 +5,48 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider, type UseQueryResult } from '@tanstack/react-query'; +import * as ReactQuery from '@tanstack/react-query'; + +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { TransformList } from './transform_list'; +const useQueryMock = jest.spyOn(ReactQuery, 'useQuery').mockImplementation((queryKey) => { + switch (queryKey[0]) { + case 'transform.data_view_exists': + return { error: null, data: true } as UseQueryResult; + } + + return { error: null, data: undefined } as UseQueryResult; +}); + +const queryClient = new QueryClient(); + jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); describe('Transform: Transform List ', () => { - test('Minimal initialization', () => { - const wrapper = shallow( - + test('Minimal initialization', async () => { + const { container } = render( + + + + + ); - expect(wrapper).toMatchSnapshot(); + await waitFor(() => { + expect(useQueryMock).toHaveBeenCalledTimes(4); + expect(container.textContent).toContain('Create your first transform'); + }); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx index dc831e996b309..2e0106afc0e92 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { FC, MouseEventHandler, useContext, useState } from 'react'; +import React, { type FC, type MouseEventHandler, useState } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiButton, @@ -26,12 +27,10 @@ import { useReauthorizeAction, } from '../action_reauthorize'; import type { TransformId } from '../../../../../../common/types/transform'; -import { - TRANSFORM_LIST_COLUMN, - TransformListRow, - useRefreshTransformList, -} from '../../../../common'; -import { AuthorizationContext } from '../../../../lib/authorization'; + +import { type TransformListRow, TRANSFORM_LIST_COLUMN } from '../../../../common'; +import { useRefreshTransformList, useTransformCapabilities } from '../../../../hooks'; + import { CreateTransformButton } from '../create_transform_button'; import { RefreshTransformListButton } from '../refresh_transform_list_button'; import { @@ -83,6 +82,7 @@ function getItemIdToExpandedRowMap( } interface TransformListProps { + isLoading: boolean; onCreateTransform: MouseEventHandler; transformNodes: number; transforms: TransformListRow[]; @@ -90,13 +90,13 @@ interface TransformListProps { } export const TransformList: FC = ({ + isLoading, onCreateTransform, transformNodes, transforms, transformsLoading, }) => { - const [isLoading, setIsLoading] = useState(false); - const { refresh } = useRefreshTransformList({ isLoading: setIsLoading }); + const refreshTransformList = useRefreshTransformList(); const { setEditAlertRule } = useAlertRuleFlyout(); const [query, setQuery] = useState>[0]>(); @@ -111,7 +111,7 @@ export const TransformList: FC = ({ const bulkStopAction = useStopAction(false); const bulkScheduleNowAction = useScheduleNowAction(false, transformNodes); - const { capabilities } = useContext(AuthorizationContext); + const capabilities = useTransformCapabilities(); const disabled = !capabilities.canCreateTransform || !capabilities.canPreviewTransform || @@ -134,10 +134,6 @@ export const TransformList: FC = ({ const filteredTransforms = clauses.length > 0 ? filterTransforms(transforms, clauses) : transforms; - if (transforms.length === 0 && transformNodes === 0) { - return null; - } - if (transforms.length === 0) { return ( = ({ const toolsRight = ( - + diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx index 8ad53c64c2f1e..a041466eb5434 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transforms_stats_bar.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; -import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -16,7 +16,7 @@ import { TRANSFORM_MODE, TRANSFORM_STATE } from '../../../../../../common/consta import { TransformListRow } from '../../../../common'; -import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; +import { useDocumentationLinks, useRefreshTransformList } from '../../../../hooks'; import { StatsBar, TransformStatsBarStats } from '../stats_bar'; @@ -109,6 +109,7 @@ export const TransformStatsBar: FC = ({ transformNodes, transformsList, }) => { + const refreshTransformList = useRefreshTransformList(); const { esNodeRoles } = useDocumentationLinks(); const transformStats: TransformStatsBarStats = createTranformStats( @@ -118,10 +119,8 @@ export const TransformStatsBar: FC = ({ return ( <> - {transformNodes === 0 && ( <> - = ({ }} />

+ refreshTransformList()} size="s"> + +
+ )} + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx index d620a60e7a861..34b69e45f087a 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import React, { type FC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; jest.mock('../../../../../shared_imports'); @@ -14,8 +16,13 @@ import { useActions } from './use_actions'; describe('Transform: Transform List Actions', () => { test('useActions()', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useActions({ forceDisable: false, transformNodes: 1 }) + const queryClient = new QueryClient(); + const wrapper: FC = ({ children }) => ( + {children} + ); + const { result, waitForNextUpdate } = renderHook( + () => useActions({ forceDisable: false, transformNodes: 1 }), + { wrapper } ); await waitForNextUpdate(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx index 521bd745e8692..87f550e433bc2 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import React, { type FC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; import { useColumns } from './use_columns'; @@ -14,7 +16,13 @@ jest.mock('../../../../app_dependencies'); describe('Transform: Job List Columns', () => { test('useColumns()', async () => { - const { result, waitForNextUpdate } = renderHook(() => useColumns([], () => {}, 1, [])); + const queryClient = new QueryClient(); + const wrapper: FC = ({ children }) => ( + {children} + ); + const { result, waitForNextUpdate } = renderHook(() => useColumns([], () => {}, 1, []), { + wrapper, + }); await waitForNextUpdate(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx index db77b3e306da2..2ae8edf30a1de 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useContext } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -24,7 +24,7 @@ import { EuiIcon, } from '@elastic/eui'; -import { AuthorizationContext } from '../../../../lib/authorization'; +import { useTransformCapabilities } from '../../../../hooks'; import { needsReauthorization } from '../../../../common/reauthorization_utils'; import { isLatestTransform, @@ -52,7 +52,7 @@ export const useColumns = ( transformNodes: number, transformSelection: TransformListRow[] ) => { - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { canStartStopTransform } = useTransformCapabilities(); const { actions, modals } = useActions({ forceDisable: transformSelection.length > 0, diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts deleted file mode 100644 index c1f4c3d4f7d5c..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 React, { useEffect } from 'react'; - -import { DEFAULT_REFRESH_INTERVAL_MS } from '../../../../../../common/constants'; - -import { useRefreshTransformList } from '../../../../common'; - -export const useRefreshInterval = ( - setBlockRefresh: React.Dispatch> -) => { - const { refresh } = useRefreshTransformList(); - useEffect(() => { - const interval = setInterval(refresh, DEFAULT_REFRESH_INTERVAL_MS); - - // useEffect cleanup - return () => { - clearInterval(interval); - }; - // custom comparison - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); // [] as comparator makes sure this only runs once -}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx index 3e4cee609c75b..3782bc9e9dfb3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx @@ -5,17 +5,25 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { TransformManagementSection } from './transform_management_section'; jest.mock('../../../shared_imports'); +jest.mock('../../services/navigation'); + +const queryClient = new QueryClient(); describe('Transform: ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); + const { container } = render( + + + + ); - expect(wrapper).toMatchSnapshot(); + expect(container.textContent).toContain('Missing permission'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx index 093930aeb1b7f..8cfb7ebb5240c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { FC, useContext, useEffect, useMemo, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; +import React, { type FC, useEffect, useMemo, useState } from 'react'; + import { EuiButton, EuiButtonEmpty, @@ -16,19 +16,26 @@ import { EuiSkeletonText, EuiSpacer, } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; + import { needsReauthorization } from '../../common/reauthorization_utils'; +import { TRANSFORM_STATE } from '../../../../common/constants'; + import { - APP_GET_TRANSFORM_CLUSTER_PRIVILEGES, - TRANSFORM_STATE, -} from '../../../../common/constants'; -import { TransformListRow, useRefreshTransformList } from '../../common'; -import { useDocumentationLinks } from '../../hooks/use_documentation_links'; -import { useDeleteTransforms, useGetTransforms } from '../../hooks'; + useDocumentationLinks, + useDeleteTransforms, + useTransformCapabilities, + useGetTransforms, + useGetTransformNodes, +} from '../../hooks'; import { RedirectToCreateTransform } from '../../common/navigation'; -import { AuthorizationContext, PrivilegesWrapper } from '../../lib/authorization'; -import { BREADCRUMB_SECTION, breadcrumbService, docTitleService } from '../../services/navigation'; -import { useRefreshInterval } from './components/transform_list/use_refresh_interval'; +import { CapabilitiesWrapper } from '../../components/capabilities_wrapper'; +import { ToastNotificationText } from '../../components/toast_notification_text'; +import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; + import { SearchSelection } from './components/search_selection'; import { TransformList } from './components/transform_list'; import { TransformStatsBar } from './components/transform_list/transforms_stats_bar'; @@ -38,39 +45,52 @@ import { TransformAlertFlyoutWrapper, } from '../../../alerting/transform_alerting_flyout'; +const ErrorMessageCallout: FC<{ + text: JSX.Element; + errorMessage: IHttpFetchError | null; +}> = ({ text, errorMessage }) => { + return ( + <> + + + {text}{' '} + {errorMessage !== null && ( + + )} + + } + color="danger" + iconType="error" + /> + + ); +}; + export const TransformManagement: FC = () => { const { esTransform } = useDocumentationLinks(); - const [transformsLoading, setTransformsLoading] = useState(false); - const [isInitialized, setIsInitialized] = useState(false); - const [blockRefresh, setBlockRefresh] = useState(false); - const [transforms, setTransforms] = useState([]); - const [transformNodes, setTransformNodes] = useState(0); - const [errorMessage, setErrorMessage] = useState(undefined); - const [transformIdsWithoutConfig, setTransformIdsWithoutConfig] = useState< - string[] | undefined - >(); - const deleteTransforms = useDeleteTransforms(); - const getTransforms = useGetTransforms( - setTransforms, - setTransformNodes, - setErrorMessage, - setTransformIdsWithoutConfig, - setIsInitialized, - blockRefresh - ); + const { + isInitialLoading: transformNodesInitialLoading, + error: transformNodesErrorMessage, + data: transformNodesData = 0, + } = useGetTransformNodes(); + const transformNodes = transformNodesErrorMessage === null ? transformNodesData : 0; - // Subscribe to the refresh observable to trigger reloading the transform list. - useRefreshTransformList({ - isLoading: setTransformsLoading, - onRefresh: () => getTransforms(true), - }); - // Call useRefreshInterval() after the subscription above is set up. - useRefreshInterval(setBlockRefresh); + const { + isInitialLoading: transformsInitialLoading, + isLoading: transformsLoading, + error: transformsErrorMessage, + data: { transforms, transformIdsWithoutConfig }, + } = useGetTransforms({ enabled: !transformNodesInitialLoading && transformNodes > 0 }); - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const isInitialLoading = transformNodesInitialLoading || transformsInitialLoading; + + const { canStartStopTransform } = useTransformCapabilities(); const unauthorizedTransformsWarning = useMemo(() => { const unauthorizedCnt = transforms.filter((t) => needsReauthorization(t)).length; @@ -162,93 +182,101 @@ export const TransformManagement: FC = () => { paddingSize={'none'} /> - - - {typeof errorMessage !== 'undefined' && ( - - - - } - body={ -

-

{JSON.stringify(errorMessage)}
-

- } - /> - )} - - {!isInitialized && } - {isInitialized && ( + {isInitialLoading && ( + <> + + + + )} + {!isInitialLoading && ( <> {unauthorizedTransformsWarning} + {transformNodesErrorMessage !== null && ( + + } + errorMessage={transformNodesErrorMessage} + /> + )} + {transformsErrorMessage !== null && ( + + } + errorMessage={transformsErrorMessage} + /> + )} + + - {typeof errorMessage === 'undefined' && ( - - {transformIdsWithoutConfig ? ( - <> - -

- -

- { - await deleteTransforms( - // If transform task doesn't have any corresponding config - // we won't know what the destination index or data view would be - // and should be force deleted - { - transformsInfo: transformIdsWithoutConfig.map((id) => ({ - id, - state: TRANSFORM_STATE.FAILED, - })), - deleteDestIndex: false, - deleteDestDataView: false, - forceDelete: true, - } - ); + + {transformIdsWithoutConfig ? ( + <> + +

+ - - - - - - ) : null} + /> +

+ + deleteTransforms( + // If transform task doesn't have any corresponding config + // we won't know what the destination index or data view would be + // and should be force deleted + { + transformsInfo: transformIdsWithoutConfig.map((id) => ({ + id, + state: TRANSFORM_STATE.FAILED, + })), + deleteDestIndex: false, + deleteDestDataView: false, + forceDelete: true, + } + ) + } + > + + +
+ + + ) : null} + {(transformNodes > 0 || transforms.length > 0) && ( - -
- )} + )} + +
)}
@@ -274,8 +302,8 @@ export const TransformManagementSection: FC = () => { }, []); return ( - + - + ); }; diff --git a/x-pack/plugins/transform/public/app/services/es_index_service.ts b/x-pack/plugins/transform/public/app/services/es_index_service.ts deleted file mode 100644 index d8d058b731a74..0000000000000 --- a/x-pack/plugins/transform/public/app/services/es_index_service.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 type { HttpSetup } from '@kbn/core/public'; -import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { addInternalBasePath } from '../../../common/constants'; - -export class IndexService { - async canDeleteIndex(http: HttpSetup) { - const privilege = await http.get<{ hasAllPrivileges: boolean }>( - addInternalBasePath(`privileges`), - { version: '1' } - ); - if (!privilege) { - return false; - } - return privilege.hasAllPrivileges; - } - - async dataViewExists(dataViewsContract: DataViewsContract, indexName: string) { - return (await dataViewsContract.find(indexName)).some(({ title }) => title === indexName); - } -} - -export const indexService = new IndexService(); diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts b/x-pack/plugins/transform/server/capabilities.test.ts similarity index 94% rename from x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts rename to x-pack/plugins/transform/server/capabilities.test.ts index e9fa6cd7bc6d7..5cf194d6e84e3 100644 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts +++ b/x-pack/plugins/transform/server/capabilities.test.ts @@ -5,7 +5,9 @@ * 2.0. */ -import { extractMissingPrivileges, getPrivilegesAndCapabilities } from './has_privilege_factory'; +import { type TransformCapabilities } from '../common/types/capabilities'; + +import { extractMissingPrivileges, getPrivilegesAndCapabilities } from './capabilities'; describe('has_privilege_factory', () => { const fullClusterPrivileges = { @@ -65,9 +67,10 @@ describe('has_privilege_factory', () => { describe('getPrivilegesAndCapabilities', () => { it('returns full capabilities if provided both monitor and admin cluster privileges', () => { - const fullCapabilities = { + const fullCapabilities: TransformCapabilities = { canCreateTransform: true, canCreateTransformAlerts: true, + canDeleteIndex: true, canDeleteTransform: true, canGetTransform: true, canPreviewTransform: true, @@ -91,9 +94,10 @@ describe('has_privilege_factory', () => { }); }); it('returns view only capabilities if provided only monitor cluster privileges', () => { - const viewOnlyCapabilities = { + const viewOnlyCapabilities: TransformCapabilities = { canCreateTransform: false, canCreateTransformAlerts: false, + canDeleteIndex: false, canDeleteTransform: false, canGetTransform: true, canPreviewTransform: false, @@ -119,9 +123,10 @@ describe('has_privilege_factory', () => { }); }); it('returns no capabilities and all the missing privileges if no cluster privileges', () => { - const noCapabilities = { + const noCapabilities: TransformCapabilities = { canCreateTransform: false, canCreateTransformAlerts: false, + canDeleteIndex: false, canDeleteTransform: false, canGetTransform: false, canPreviewTransform: false, diff --git a/x-pack/plugins/transform/server/capabilities.ts b/x-pack/plugins/transform/server/capabilities.ts index 73889a0808359..5fd542d428a70 100644 --- a/x-pack/plugins/transform/server/capabilities.ts +++ b/x-pack/plugins/transform/server/capabilities.ts @@ -7,22 +7,123 @@ import type { CoreSetup } from '@kbn/core-lifecycle-server'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + import { - getPrivilegesAndCapabilities, - INITIAL_CAPABILITIES, -} from '../common/privilege/has_privilege_factory'; -import { APP_CLUSTER_PRIVILEGES } from '../common/constants'; + getInitialTransformCapabilities, + type Privilege, + type Privileges, + type PrivilegesAndCapabilities, +} from '../common/types/capabilities'; +import { APP_CLUSTER_PRIVILEGES, APP_INDEX_PRIVILEGES } from '../common/constants'; + import type { PluginStartDependencies } from './types'; export const TRANSFORM_PLUGIN_ID = 'transform' as const; +function isPrivileges(arg: unknown): arg is Privileges { + return ( + isPopulatedObject(arg, ['hasAllPrivileges', 'missingPrivileges']) && + typeof arg.hasAllPrivileges === 'boolean' && + typeof arg.missingPrivileges === 'object' && + arg.missingPrivileges !== null + ); +} + +export const hasPrivilegeFactory = + (privileges: Privileges | undefined | null) => (privilege: Privilege) => { + const [section, requiredPrivilege] = privilege; + if (isPrivileges(privileges) && !privileges.missingPrivileges[section]) { + // if the section does not exist in our missingPrivileges, everything is OK + return true; + } + if (isPrivileges(privileges) && privileges.missingPrivileges[section]!.length === 0) { + return true; + } + if (requiredPrivilege === '*') { + // If length > 0 and we require them all... KO + return false; + } + // If we require _some_ privilege, we make sure that the one + // we require is *not* in the missingPrivilege array + return ( + isPrivileges(privileges) && + !privileges.missingPrivileges[section]!.includes(requiredPrivilege) + ); + }; + +export const extractMissingPrivileges = ( + privilegesObject: { [key: string]: boolean } = {} +): string[] => + Object.keys(privilegesObject).reduce((privileges: string[], privilegeName: string): string[] => { + if (!privilegesObject[privilegeName]) { + privileges.push(privilegeName); + } + return privileges; + }, []); + +export const getPrivilegesAndCapabilities = ( + clusterPrivileges: Record, + hasOneIndexWithAllPrivileges: boolean, + hasAllPrivileges: boolean +): PrivilegesAndCapabilities => { + const privilegesResult: Privileges = { + hasAllPrivileges: true, + missingPrivileges: { + cluster: [], + index: [], + }, + }; + + // Find missing cluster privileges and set overall app privileges + privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(clusterPrivileges); + privilegesResult.hasAllPrivileges = hasAllPrivileges; + + if (!hasOneIndexWithAllPrivileges) { + privilegesResult.missingPrivileges.index = [...APP_INDEX_PRIVILEGES]; + } + + const hasPrivilege = hasPrivilegeFactory(privilegesResult); + + const capabilities = getInitialTransformCapabilities(); + + capabilities.canGetTransform = + hasPrivilege(['cluster', 'cluster:monitor/transform/get']) && + hasPrivilege(['cluster', 'cluster:monitor/transform/stats/get']); + + capabilities.canCreateTransform = hasPrivilege(['cluster', 'cluster:admin/transform/put']); + + capabilities.canDeleteTransform = hasPrivilege(['cluster', 'cluster:admin/transform/delete']); + + capabilities.canResetTransform = hasPrivilege(['cluster', 'cluster:admin/transform/reset']); + + capabilities.canPreviewTransform = hasPrivilege(['cluster', 'cluster:admin/transform/preview']); + + capabilities.canStartStopTransform = + hasPrivilege(['cluster', 'cluster:admin/transform/start']) && + hasPrivilege(['cluster', 'cluster:admin/transform/start_task']) && + hasPrivilege(['cluster', 'cluster:admin/transform/stop']); + + capabilities.canCreateTransformAlerts = capabilities.canCreateTransform; + + capabilities.canUseTransformAlerts = capabilities.canGetTransform; + + capabilities.canScheduleNowTransform = capabilities.canStartStopTransform; + + capabilities.canReauthorizeTransform = capabilities.canStartStopTransform; + + capabilities.canDeleteIndex = hasAllPrivileges; + + return { privileges: privilegesResult, capabilities }; +}; + export const setupCapabilities = ( core: Pick, 'capabilities' | 'getStartServices'>, securitySetup?: SecurityPluginSetup ) => { core.capabilities.registerProvider(() => { return { - transform: INITIAL_CAPABILITIES, + transform: getInitialTransformCapabilities(), }; }); @@ -38,10 +139,7 @@ export const setupCapabilities = ( // If security is not enabled or not available, transform should have full permission if (!isSecurityPluginEnabled || !securityStart) { return { - transform: Object.keys(INITIAL_CAPABILITIES).reduce>((acc, p) => { - acc[p] = true; - return acc; - }, {}), + transform: getInitialTransformCapabilities(true), }; } @@ -71,6 +169,8 @@ export const setupCapabilities = ( hasAllRequested ).capabilities; - return { transform: transformCapabilities }; + return { + transform: transformCapabilities as Record>, + }; }); }; diff --git a/x-pack/plugins/transform/server/routes/api/privileges.ts b/x-pack/plugins/transform/server/routes/api/privileges.ts deleted file mode 100644 index 0b93c8e503fc6..0000000000000 --- a/x-pack/plugins/transform/server/routes/api/privileges.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 type { IKibanaResponse, IScopedClusterClient } from '@kbn/core/server'; -import type { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/types'; -import { - getPrivilegesAndCapabilities, - type PrivilegesAndCapabilities, -} from '../../../common/privilege/has_privilege_factory'; -import { - addInternalBasePath, - APP_CLUSTER_PRIVILEGES, - APP_INDEX_PRIVILEGES, -} from '../../../common/constants'; - -import type { RouteDependencies } from '../../types'; - -export function registerPrivilegesRoute({ router, license }: RouteDependencies) { - router.versioned - .get({ - path: addInternalBasePath('privileges'), - access: 'internal', - }) - .addVersion( - { - version: '1', - validate: false, - }, - license.guardApiRoute( - async (ctx, req, res): Promise> => { - if (license.getStatus().isSecurityEnabled === false) { - // If security isn't enabled, let the user use app. - return res.ok({ - body: getPrivilegesAndCapabilities({}, true, true), - }); - } - - const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client; - - const esClusterPrivilegesReq: Promise = - esClient.asCurrentUser.security.hasPrivileges({ - body: { - cluster: APP_CLUSTER_PRIVILEGES, - }, - }); - const [esClusterPrivileges, userPrivileges] = await Promise.all([ - // Get cluster privileges - esClusterPrivilegesReq, - // // Get all index privileges the user has - esClient.asCurrentUser.security.getUserPrivileges(), - ]); - - const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges; - const { indices } = userPrivileges; - - // Check if they have all the required index privileges for at least one index - const hasOneIndexWithAllPrivileges = - indices.find(({ privileges }: { privileges: string[] }) => { - if (privileges.includes('all')) { - return true; - } - - const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) => - privileges.includes(privilege) - ); - - return indexHasAllPrivileges; - }) !== undefined; - - return res.ok({ - body: getPrivilegesAndCapabilities( - cluster, - hasOneIndexWithAllPrivileges, - hasAllPrivileges - ), - }); - } - ) - ); -} diff --git a/x-pack/plugins/transform/server/routes/index.ts b/x-pack/plugins/transform/server/routes/index.ts index 0a8539c7683de..aec4417f2a421 100644 --- a/x-pack/plugins/transform/server/routes/index.ts +++ b/x-pack/plugins/transform/server/routes/index.ts @@ -8,11 +8,9 @@ import { RouteDependencies } from '../types'; import { registerFieldHistogramsRoutes } from './api/field_histograms'; -import { registerPrivilegesRoute } from './api/privileges'; import { registerTransformsRoutes } from './api/transforms'; export function registerRoutes(dependencies: RouteDependencies) { registerFieldHistogramsRoutes(dependencies); - registerPrivilegesRoute(dependencies); registerTransformsRoutes(dependencies); } diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json index 9c9ef2299c60d..2d0cdf45c554d 100644 --- a/x-pack/plugins/transform/tsconfig.json +++ b/x-pack/plugins/transform/tsconfig.json @@ -67,7 +67,8 @@ "@kbn/saved-search-plugin", "@kbn/unified-field-list", "@kbn/ebt-tools", - "@kbn/content-management-plugin" + "@kbn/content-management-plugin", + "@kbn/react-kibana-mount" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a7c61f6934865..55fe275930510 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -37699,26 +37699,19 @@ "xpack.transform.alertTypes.transformHealth.healthCheckRecoveryMessage": "La/les {count, plural, one {Transformer} many {Transformations} other {Transformations}} {transformsString} {count, plural, one {est} many {sont} other {sont}} saine(s).", "xpack.transform.alertTypes.transformHealth.notStartedMessage": "La/les {count, plural, one {Transformer} many {Transformer} other {Transformer}} {transformsString} {count, plural, one {est} many {sont} other {ne sont pas démarrées}}.", "xpack.transform.alertTypes.transformHealth.notStartedRecoveryMessage": "La/les {count, plural, one {Transformer} many {Transformer} other {Transformer}} {transformsString} {count, plural, one {est} many {sont} other {sont}} lancée(s).", - "xpack.transform.app.deniedPrivilegeDescription": "Pour utiliser cette section des transformations, vous devez avoir {privilegesCount, plural, one {ce privilège de cluster} many {ces privilèges de cluster} other {ces privilèges de cluster}} : {missingPrivileges}.", "xpack.transform.capability.pleaseContactAdministratorTooltip": "{message} Veuillez contacter votre administrateur.", "xpack.transform.clone.noDataViewErrorPromptText": "Impossible de cloner la transformation {transformId}. Il n'existe aucune vue de données pour {dataViewTitle}.", "xpack.transform.danglingTasksError": "{count} {count, plural, one {transformation n'a pas de} many {transformations n'ont pas de} other {transformations n'ont pas de}} détails de configuration : [{transformIds}] {count, plural, one {Elle ne peut pas être récupérée et doit être supprimée} many {Elles ne peuvent pas être récupérées et doivent être supprimées} other {Elles ne peuvent pas être récupérées et doivent être supprimées}}.", "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage": "Une erreur est survenue lors de la suppression de la vue de données {destinationIndex}", - "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage": "Requête de suppression de la vue de données {destinationIndex} reconnue.", "xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "Une erreur s'est produite lors de la suppression de l'index de destination {destinationIndex}", - "xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage": "Requête de suppression de l'index de destination {destinationIndex} reconnue.", "xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage": "Une erreur s'est produite lors de la vérification de l'existence de la vue de données {dataView} : {error}", "xpack.transform.edit.noDataViewErrorPromptText": "Impossible d'obtenir la vue de données pour la transformation {transformId}. Il n'existe aucune vue de données pour {dataViewTitle}.", "xpack.transform.forceDeleteTransformMessage": "Supprimer {count} {count, plural, one {transformation} many {transformations} other {transformations}}", "xpack.transform.managedTransformsWarningCallout": "{count, plural, one {Cette transformation} many {Au moins l'une de ces transformations} other {Au moins l'une de ces transformations}} est préconfigurée par Elastic. Le fait de {action} {count, plural, one {la} many {les} other {les}} avec une heure de fin spécifique peut avoir un impact sur d'autres éléments du produit.", "xpack.transform.multiTransformActionsMenu.transformsCount": "Sélection effectuée de {count} {count, plural, one {transformation} many {transformations} other {transformations}}", "xpack.transform.stepCreateForm.createDataViewErrorMessage": "Une erreur est survenue lors de la création de la vue de données Kibana {dataViewName} :", - "xpack.transform.stepCreateForm.createDataViewSuccessMessage": "La vue de données Kibana {dataViewName} a bien été créée.", "xpack.transform.stepCreateForm.createTransformErrorMessage": "Une erreur s'est produite lors de la création de la transformation {transformId} :", - "xpack.transform.stepCreateForm.createTransformSuccessMessage": "La requête pour créer la transformation {transformId} a été reconnue.", "xpack.transform.stepCreateForm.duplicateDataViewErrorMessage": "Une erreur est survenue lors de la création de la vue de données Kibana {dataViewName} : La vue de données existe déjà.", - "xpack.transform.stepCreateForm.startTransformErrorMessage": "Une erreur s'est produite lors du démarrage de la transformation {transformId} :", - "xpack.transform.stepCreateForm.startTransformSuccessMessage": "La requête pour démarrer la transformation {transformId} a été reconnue.", "xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar": "Requête non valide : {queryErrorMessage}", "xpack.transform.stepDefineForm.queryPlaceholderKql": "Par exemple, {example}", "xpack.transform.stepDefineForm.queryPlaceholderLucene": "Par exemple, {example}", @@ -37730,41 +37723,30 @@ "xpack.transform.stepDetailsForm.retentionPolicyMaxAgePlaceholderText": "max_age, par exemple {exampleValue}", "xpack.transform.transformForm.sizeNotationPlaceholder": "Exemples : {example1}, {example2}, {example3}, {example4}", "xpack.transform.transformList.alertingRules.tooltipContent": "La transformation a {rulesCount} {rulesCount, plural, one { règle} many { règles} other { règles}} d'alerte associée(s)", - "xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage": "Suppression réussie de {count} {count, plural, one {vue} many {vues} other {vues}} de données de destination.", - "xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "Suppression réussie de {count} {count, plural, one {index} many {index système non migrés} other {index}} de destination.", "xpack.transform.transformList.bulkDeleteModalTitle": "Supprimer {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", - "xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "Suppression réussie de {count} {count, plural, one {transformation} many {transformations} other {transformations}}.", "xpack.transform.transformList.bulkReauthorizeModalTitle": "Réautoriser {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", "xpack.transform.transformList.bulkResetModalTitle": "Réinitialiser {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", - "xpack.transform.transformList.bulkResetTransformSuccessMessage": "Réinitialisation effectuée avec succès de {count} {count, plural, one {transformation} many {transformations} other {transformations}}.", "xpack.transform.transformList.bulkStartModalTitle": "Démarrer {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", "xpack.transform.transformList.bulkStopModalTitle": "Arrêter {count} {count, plural, one {transformation} many {transformations} other {transformations}} ?", "xpack.transform.transformList.cannotRestartCompleteBatchTransformToolTip": "{transformId} est une transformation par lots terminée et ne peut pas être redémarrée.", "xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformToolTip": "{transformId} est une transformation par lots terminée qui ne peut pas être planifiée pour traiter les données instantanément.", "xpack.transform.transformList.deleteModalTitle": "Supprimer {transformId} ?", "xpack.transform.transformList.deleteTransformErrorMessage": "Une erreur s'est produite lors de la suppression de la transformation {transformId}", - "xpack.transform.transformList.deleteTransformSuccessMessage": "La requête pour supprimer la transformation {transformId} a été reconnue.", "xpack.transform.transformList.editFlyoutFormPlaceholderText": "Par défaut : {defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "Modifier {transformId}", - "xpack.transform.transformList.editTransformSuccessMessage": "Transformation {transformId} mise à jour.", "xpack.transform.transformList.reauthorizeModalTitle": "Réautoriser {transformId} ?", "xpack.transform.transformList.reauthorizeTransformErrorMessage": "Une erreur s'est produite lors de la réautorisation de la transformation {transformId}", - "xpack.transform.transformList.reauthorizeTransformSuccessMessage": "La requête pour réautoriser la transformation {transformId} a été reconnue.", "xpack.transform.transformList.resetModalTitle": "Réinitialiser {transformId} ?", "xpack.transform.transformList.resetTransformErrorMessage": "Une erreur s'est produite lors de la réinitialisation de la transformation {transformId}", - "xpack.transform.transformList.resetTransformSuccessMessage": "La requête pour réinitialiser la transformation {transformId} a été reconnue.", "xpack.transform.transformList.rowCollapse": "Masquer les détails pour {transformId}", "xpack.transform.transformList.rowExpand": "Afficher les détails pour {transformId}", "xpack.transform.transformList.scheduleNowTransformErrorMessage": "Une erreur s'est produite lors de la planification de la transformation {transformId} pour traiter les données instantanément.", - "xpack.transform.transformList.scheduleNowTransformSuccessMessage": "La demande de planification de transformation {transformId} pour traiter les données immédiatement a été reconnue.", "xpack.transform.transformList.startedTransformToolTip": "{transformId} a déjà démarré.", "xpack.transform.transformList.startModalTitle": "Démarrer {transformId} ?", "xpack.transform.transformList.startTransformErrorMessage": "Une erreur s'est produite lors du démarrage de la transformation {transformId}", - "xpack.transform.transformList.startTransformSuccessMessage": "La requête pour démarrer la transformation {transformId} a été reconnue.", "xpack.transform.transformList.stopModalTitle": "Arrêter {transformId} ?", "xpack.transform.transformList.stoppedTransformToolTip": "{transformId} est déjà arrêtée.", "xpack.transform.transformList.stopTransformErrorMessage": "Une erreur s'est produite lors de l'arrêt de la transformation du cadre de données {transformId}", - "xpack.transform.transformList.stopTransformSuccessMessage": "La requête pour arrêter la transformation du cadre de données {transformId} a été reconnue.", "xpack.transform.transformList.unauthorizedTransformsCallout.insufficientPermissionsMsg": "{unauthorizedCnt, plural, one {Une transformation a été créée avec des autorisations insuffisantes.} many {# transformations ont été créées avec des autorisations insuffisantes.} other {# transformations ont été créées avec des autorisations insuffisantes.}}", "xpack.transform.transformList.unauthorizedTransformsCallout.reauthorizeMsg": "Réautorisez pour démarrer {unauthorizedCnt, plural, one {la transformation} many {# transformations} other {# transformations}}.", "xpack.transform.transformNodes.noTransformNodesCallOutBody": "Vous ne pourrez ni créer ni exécuter de transformations. {learnMoreLink}", @@ -37810,9 +37792,6 @@ "xpack.transform.alertTypes.transformHealth.notStartedCheckName": "La transformation n'a pas démarré", "xpack.transform.alertTypes.transformHealth.testsConfigTransforms.errorMessage": "Au moins une vérification d'intégrité doit être sélectionnée", "xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel": "Activer", - "xpack.transform.app.checkingPrivilegesDescription": "Vérification des privilèges…", - "xpack.transform.app.checkingPrivilegesErrorMessage": "Erreur lors de la récupération des privilèges utilisateur depuis le serveur", - "xpack.transform.app.deniedPrivilegeTitle": "Vous ne disposez pas de privilèges de cluster", "xpack.transform.appName": "Transformations", "xpack.transform.appTitle": "Transformations", "xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip": "Vous ne disposez pas d'autorisation pour créer des règles d'alerte de transformation.", @@ -37858,7 +37837,6 @@ "xpack.transform.licenseCheckErrorMessage": "La vérification de la licence a échoué", "xpack.transform.list.emptyPromptButtonText": "Créez votre première transformation", "xpack.transform.list.emptyPromptTitle": "Aucune transformation n'a été trouvée", - "xpack.transform.list.errorPromptTitle": "Une erreur s'est produite lors de l'obtention de la liste de transformations", "xpack.transform.mode": "Mode", "xpack.transform.modeFilter": "Mode", "xpack.transform.models.transformService.allOtherRequestsCancelledDescription": "Toutes les autres requêtes ont été annulées.", @@ -38090,7 +38068,6 @@ "xpack.transform.transformList.editFlyoutUpdateButtonText": "Mettre à jour", "xpack.transform.transformList.editManagedTransformsDescription": "modification", "xpack.transform.transformList.editTransformGenericErrorMessage": "Une erreur s'est produite lors de l'appel du point de terminaison de l'API pour mettre à jour les transformations.", - "xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "Une erreur s'est produite lors de la vérification de la possibilité pour un utilisateur de supprimer l'index de destination", "xpack.transform.transformList.managedBadgeLabel": "Géré", "xpack.transform.transformList.managedBadgeTooltip": "Cette transformation est préconfigurée et gérée par Elastic. Les autres éléments du produit peuvent dépendre de ce comportement.", "xpack.transform.transformList.needsReauthorizationBadge.contactAdminTooltip": "Contactez votre administrateur pour demander les autorisations requises.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4248b9bc241b2..84fc3b101eb4e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -37698,26 +37698,19 @@ "xpack.transform.alertTypes.transformHealth.healthCheckRecoveryMessage": "{count, plural, other {トランスフォーム}} {transformsString} {count, plural, other {あります}}は正常です。", "xpack.transform.alertTypes.transformHealth.notStartedMessage": "{count, plural, other {トランスフォーム}}\"{transformsString}\"{count, plural, other {あります}}開始されていません。", "xpack.transform.alertTypes.transformHealth.notStartedRecoveryMessage": "{count, plural, other {トランスフォーム}}\"{transformsString}\"{count, plural, other {あります}}開始しました。", - "xpack.transform.app.deniedPrivilegeDescription": "変換のこのセクションを使用するには、{privilegesCount, plural, other {これらのクラスター権限}}が必要です:{missingPrivileges}", "xpack.transform.capability.pleaseContactAdministratorTooltip": "{message} 管理者にお問い合わせください。", "xpack.transform.clone.noDataViewErrorPromptText": "トランスフォーム{transformId}を複製できません。{dataViewTitle}のデータビューは存在しません。", "xpack.transform.danglingTasksError": "{count}個の{count, plural, other {変換}}に構成の詳細がありません:[{transformIds}]。{count, plural, other {それら}}回復できないため、削除してください。", "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage": "データビュー{destinationIndex}の削除中にエラーが発生しました", - "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage": "データビュー {destinationIndex} の削除リクエストが受け付けられました。", "xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "ディスティネーションインデックス{destinationIndex}の削除中にエラーが発生しました", - "xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage": "ディスティネーションインデックス{destinationIndex}を削除する要求が確認されました。", "xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage": "データビュー{dataView}が存在するかどうかを確認するときにエラーが発生しました:{error}", "xpack.transform.edit.noDataViewErrorPromptText": "変換{transformId}のデータビューを取得できません。{dataViewTitle}のデータビューは存在しません。", "xpack.transform.forceDeleteTransformMessage": "{count}{count, plural, other {変換}}の削除", "xpack.transform.managedTransformsWarningCallout": "{count, plural, other {これらの変換のうちの少なくとも1個の変換}}はElasticによってあらかじめ構成されています。{count, plural, other {それらを}}{action}すると、製品の他の部分に影響する可能性があります。", "xpack.transform.multiTransformActionsMenu.transformsCount": "{count}個の{count, plural, other {変換}}を選択済み", "xpack.transform.stepCreateForm.createDataViewErrorMessage": "Kibanaデータビュー{dataViewName}の作成中にエラーが発生しました:", - "xpack.transform.stepCreateForm.createDataViewSuccessMessage": "Kibanaデータビュー{dataViewName}が正常に作成されました。", "xpack.transform.stepCreateForm.createTransformErrorMessage": "変換 {transformId} の取得中にエラーが発生しました。", - "xpack.transform.stepCreateForm.createTransformSuccessMessage": "変換 {transformId} の作成リクエストが受け付けられました。", "xpack.transform.stepCreateForm.duplicateDataViewErrorMessage": "Kibanaデータビュー{dataViewName}の作成中にエラーが発生しました:データビューはすでに存在します。", - "xpack.transform.stepCreateForm.startTransformErrorMessage": "変換 {transformId} の開始中にエラーが発生しました。", - "xpack.transform.stepCreateForm.startTransformSuccessMessage": "変換 {transformId} の開始リクエストが受け付けられました。", "xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar": "無効なクエリ:{queryErrorMessage}", "xpack.transform.stepDefineForm.queryPlaceholderKql": "例: {example}.", "xpack.transform.stepDefineForm.queryPlaceholderLucene": "例: {example}.", @@ -37729,41 +37722,30 @@ "xpack.transform.stepDetailsForm.retentionPolicyMaxAgePlaceholderText": "max_age 例:{exampleValue}", "xpack.transform.transformForm.sizeNotationPlaceholder": "例:{example1}, {example2}, {example3}, {example4}", "xpack.transform.transformList.alertingRules.tooltipContent": "変換には{rulesCount}個の関連付けられたアラート{rulesCount, plural, other { ルール}}があります", - "xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage": "{count}個のディスティネーションデータ{count, plural, other {ビュー}}が正常に削除されました。", - "xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "{count}個のディスティネーション{count, plural, other {インデックス}}が正常に削除されました。", "xpack.transform.transformList.bulkDeleteModalTitle": "{count}{count, plural, other {変換}}を削除しますか?", - "xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "{count}個の{count, plural, other {トランスフォーム}}が正常に削除されました。", "xpack.transform.transformList.bulkReauthorizeModalTitle": "{count} {count, plural, other {トランスフォーム}}をもう一度認可しますか?", "xpack.transform.transformList.bulkResetModalTitle": "{count}個の{count, plural, other {変換}}をリセットしますか?", - "xpack.transform.transformList.bulkResetTransformSuccessMessage": "{count}個の{count, plural, other {変換}}が正常にリセットされました。", "xpack.transform.transformList.bulkStartModalTitle": "{count}個の{count, plural, other {変換}}を開始しますか?", "xpack.transform.transformList.bulkStopModalTitle": "{count}個のを{count, plural, other {変換}}停止しますか?", "xpack.transform.transformList.cannotRestartCompleteBatchTransformToolTip": "{transformId} は完了済みの一斉変換で、再度開始できません。", "xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformToolTip": "{transformId}は完了済みのバッチ変換であるため、データの即時処理のスケジュールを設定できません。", "xpack.transform.transformList.deleteModalTitle": "{transformId}を削除しますか?", "xpack.transform.transformList.deleteTransformErrorMessage": "変換 {transformId} の削除中にエラーが発生しました", - "xpack.transform.transformList.deleteTransformSuccessMessage": "変換 {transformId} の削除リクエストが受け付けられました。", "xpack.transform.transformList.editFlyoutFormPlaceholderText": "デフォルト:{defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "{transformId}の編集", - "xpack.transform.transformList.editTransformSuccessMessage": "変換{transformId}が更新されました。", "xpack.transform.transformList.reauthorizeModalTitle": "{transformId}をもう一度許可しますか?", "xpack.transform.transformList.reauthorizeTransformErrorMessage": "変換{transformId}の再認可中にエラーが発生しました", - "xpack.transform.transformList.reauthorizeTransformSuccessMessage": "変換{transformId}の再認可リクエストが受け付けられました。", "xpack.transform.transformList.resetModalTitle": "{transformId}をリセットしますか?", "xpack.transform.transformList.resetTransformErrorMessage": "変換{transformId}のリセット中にエラーが発生しました", - "xpack.transform.transformList.resetTransformSuccessMessage": "変換{transformId}のリセットリクエストが受け付けられました。", "xpack.transform.transformList.rowCollapse": "{transformId} の詳細を非表示", "xpack.transform.transformList.rowExpand": "{transformId} の詳細を表示", "xpack.transform.transformList.scheduleNowTransformErrorMessage": "変換{transformId}でデータの即時処理のスケジュールの設定中にエラーが発生しました。", - "xpack.transform.transformList.scheduleNowTransformSuccessMessage": "変換{transformId}でデータの即時処理のスケジュールを設定するリクエストが受け付けられました。", "xpack.transform.transformList.startedTransformToolTip": "{transformId} はすでに開始済みです。", "xpack.transform.transformList.startModalTitle": "{transformId}を開始しますか?", "xpack.transform.transformList.startTransformErrorMessage": "変換 {transformId} の開始中にエラーが発生しました", - "xpack.transform.transformList.startTransformSuccessMessage": "変換 {transformId} の開始リクエストが受け付けられました。", "xpack.transform.transformList.stopModalTitle": "{transformId}を終了しますか?", "xpack.transform.transformList.stoppedTransformToolTip": "{transformId} はすでに停止済みです。", "xpack.transform.transformList.stopTransformErrorMessage": "データフレーム変換 {transformId} の停止中にエラーが発生しました", - "xpack.transform.transformList.stopTransformSuccessMessage": "データフレーム変換 {transformId} の停止リクエストが受け付けられました。", "xpack.transform.transformList.unauthorizedTransformsCallout.insufficientPermissionsMsg": "{unauthorizedCnt, plural, other {十分な権限がない状態で#個の変換が作成されました。}}", "xpack.transform.transformList.unauthorizedTransformsCallout.reauthorizeMsg": "もう一度認可して、{unauthorizedCnt, plural, other {#個の変換}}を開始してください。", "xpack.transform.transformNodes.noTransformNodesCallOutBody": "変換の作成または実行はできません。{learnMoreLink}。", @@ -37809,9 +37791,6 @@ "xpack.transform.alertTypes.transformHealth.notStartedCheckName": "トランスフォームが開始していません", "xpack.transform.alertTypes.transformHealth.testsConfigTransforms.errorMessage": "1つ以上の正常性チェックを選択する必要があります", "xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel": "有効にする", - "xpack.transform.app.checkingPrivilegesDescription": "権限を確認中…", - "xpack.transform.app.checkingPrivilegesErrorMessage": "サーバーからユーザー特権を取得しているときにエラーが発生しました", - "xpack.transform.app.deniedPrivilegeTitle": "クラスター特権が足りません", "xpack.transform.appName": "トランスフォーム", "xpack.transform.appTitle": "トランスフォーム", "xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip": "トランスフォームアラートルールを作成するアクセス権がありません。", @@ -37857,7 +37836,6 @@ "xpack.transform.licenseCheckErrorMessage": "ライセンス確認失敗", "xpack.transform.list.emptyPromptButtonText": "初めてのトランスフォームを作成", "xpack.transform.list.emptyPromptTitle": "トランスフォームが見つかりません", - "xpack.transform.list.errorPromptTitle": "トランスフォームリストの取得中にエラーが発生しました", "xpack.transform.mode": "モード", "xpack.transform.modeFilter": "モード", "xpack.transform.models.transformService.allOtherRequestsCancelledDescription": "他のすべてのリクエストはキャンセルされました。", @@ -38089,7 +38067,6 @@ "xpack.transform.transformList.editFlyoutUpdateButtonText": "更新", "xpack.transform.transformList.editManagedTransformsDescription": "編集中", "xpack.transform.transformList.editTransformGenericErrorMessage": "トランスフォームを削除するためのAPIエンドポイントの呼び出し中にエラーが発生しました。", - "xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "ユーザーがディスティネーションインデックスを削除できるかどうかを確認するときにエラーが発生しました。", "xpack.transform.transformList.managedBadgeLabel": "管理中", "xpack.transform.transformList.managedBadgeTooltip": "このトランスフォームはElasticによってあらかじめ構成および管理されています。製品の他の部分はその動作に依存関係が存在している場合があります。", "xpack.transform.transformList.needsReauthorizationBadge.contactAdminTooltip": "必要な権限をリクエストするには、管理者に問い合わせてください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4eb582abc2cd2..a177efa7c53f4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -37692,26 +37692,19 @@ "xpack.transform.alertTypes.transformHealth.healthCheckRecoveryMessage": "{count, plural, other {转换}} {transformsString}{count, plural, other {有}}运行正常。", "xpack.transform.alertTypes.transformHealth.notStartedMessage": "{count, plural, other {转换}} {transformsString}{count, plural, other {有}}未启动。", "xpack.transform.alertTypes.transformHealth.notStartedRecoveryMessage": "{count, plural, other {转换}} {transformsString}{count, plural, other {有}}已启动。", - "xpack.transform.app.deniedPrivilegeDescription": "要使用“转换”部分,必须具有{privilegesCount, plural, other {以下集群权限}}:{missingPrivileges}。", "xpack.transform.capability.pleaseContactAdministratorTooltip": "{message}请联系您的管理员。", "xpack.transform.clone.noDataViewErrorPromptText": "无法克隆转换 {transformId}。对于 {dataViewTitle},不存在数据视图。", "xpack.transform.danglingTasksError": "{count} 个{count, plural, other {转换}}缺少配置详情:[{transformIds}] 无法将{count, plural, other {其}}恢复,应予以删除。", "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewErrorMessage": "删除数据视图 {destinationIndex} 时出错", - "xpack.transform.deleteTransform.deleteAnalyticsWithDataViewSuccessMessage": "删除数据视图 {destinationIndex} 的请求已确认。", "xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "删除目标索引 {destinationIndex} 时发生错误", - "xpack.transform.deleteTransform.deleteAnalyticsWithIndexSuccessMessage": "删除目标索引 {destinationIndex} 的请求已确认。", "xpack.transform.deleteTransform.errorWithCheckingIfDataViewExistsNotificationErrorMessage": "检查数据视图 {dataView} 是否存在时发生错误:{error}", "xpack.transform.edit.noDataViewErrorPromptText": "无法获取转换 {transformId} 的数据视图。对于 {dataViewTitle},不存在数据视图。", "xpack.transform.forceDeleteTransformMessage": "删除 {count} {count, plural, other {转换}}", "xpack.transform.managedTransformsWarningCallout": "{count, plural, other {至少一个此类转换}}由 Elastic 预配置;{action} {count, plural, other {这些转换}}可能会影响该产品的其他部分。", "xpack.transform.multiTransformActionsMenu.transformsCount": "已选定 {count} 个{count, plural, other {转换}}", "xpack.transform.stepCreateForm.createDataViewErrorMessage": "创建 Kibana 数据视图 {dataViewName} 时发生错误:", - "xpack.transform.stepCreateForm.createDataViewSuccessMessage": "已成功创建 Kibana 数据视图 {dataViewName}。", "xpack.transform.stepCreateForm.createTransformErrorMessage": "创建转换 {transformId} 时出错:", - "xpack.transform.stepCreateForm.createTransformSuccessMessage": "创建转换 {transformId} 的请求已确认。", "xpack.transform.stepCreateForm.duplicateDataViewErrorMessage": "创建 Kibana 数据视图 {dataViewName} 时发生错误:数据视图已存在。", - "xpack.transform.stepCreateForm.startTransformErrorMessage": "启动转换 {transformId} 时发生错误:", - "xpack.transform.stepCreateForm.startTransformSuccessMessage": "启动转换 {transformId} 的请求已确认。", "xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar": "无效查询:{queryErrorMessage}", "xpack.transform.stepDefineForm.queryPlaceholderKql": "例如,{example}", "xpack.transform.stepDefineForm.queryPlaceholderLucene": "例如,{example}", @@ -37723,41 +37716,30 @@ "xpack.transform.stepDetailsForm.retentionPolicyMaxAgePlaceholderText": "max_age,例如 {exampleValue}", "xpack.transform.transformForm.sizeNotationPlaceholder": "示例:{example1}、{example2}、{example3}、{example4}", "xpack.transform.transformList.alertingRules.tooltipContent": "转换具有 {rulesCount} 个关联的告警{rulesCount, plural, other { 规则}}", - "xpack.transform.transformList.bulkDeleteDestDataViewSuccessMessage": "已成功删除 {count} 个目标数据{count, plural, other {视图}}。", - "xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "已成功删除 {count} 个目标数据{count, plural, other {索引}}。", "xpack.transform.transformList.bulkDeleteModalTitle": "删除 {count} 个 {count, plural, other {转换}}?", - "xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "已成功删除 {count} 个{count, plural, other {转换}}。", "xpack.transform.transformList.bulkReauthorizeModalTitle": "重新授权 {count} 个{count, plural, other {转换}}?", "xpack.transform.transformList.bulkResetModalTitle": "重置 {count} 个{count, plural, other {转换}}?", - "xpack.transform.transformList.bulkResetTransformSuccessMessage": "已成功重置 {count} 个{count, plural, other {转换}}。", "xpack.transform.transformList.bulkStartModalTitle": "启动 {count} 个{count, plural, other {转换}}?", "xpack.transform.transformList.bulkStopModalTitle": "停止 {count} 个{count, plural, other {转换}}?", "xpack.transform.transformList.cannotRestartCompleteBatchTransformToolTip": "{transformId} 为已完成批量转换,无法重新启动。", "xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformToolTip": "{transformId} 为已完成的批量转换,无法安排其立即处理数据。", "xpack.transform.transformList.deleteModalTitle": "删除 {transformId}?", "xpack.transform.transformList.deleteTransformErrorMessage": "删除转换 {transformId} 时发生错误", - "xpack.transform.transformList.deleteTransformSuccessMessage": "删除转换 {transformId} 的请求已确认。", "xpack.transform.transformList.editFlyoutFormPlaceholderText": "默认值:{defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "编辑 {transformId}", - "xpack.transform.transformList.editTransformSuccessMessage": "转换 {transformId} 已更新。", "xpack.transform.transformList.reauthorizeModalTitle": "重新授权 {transformId}?", "xpack.transform.transformList.reauthorizeTransformErrorMessage": "重新授权转换 {transformId} 时出错", - "xpack.transform.transformList.reauthorizeTransformSuccessMessage": "重新授权转换 {transformId} 的请求已确认。", "xpack.transform.transformList.resetModalTitle": "重置 {transformId}?", "xpack.transform.transformList.resetTransformErrorMessage": "重置转换 {transformId} 时出错", - "xpack.transform.transformList.resetTransformSuccessMessage": "重置转换 {transformId} 的请求已确认。", "xpack.transform.transformList.rowCollapse": "隐藏 {transformId} 的详情", "xpack.transform.transformList.rowExpand": "显示 {transformId} 的详情", "xpack.transform.transformList.scheduleNowTransformErrorMessage": "计划转换 {transformId} 以立即处理数据时出错。", - "xpack.transform.transformList.scheduleNowTransformSuccessMessage": "计划转换 {transformId} 以立即处理数据的请求已确认。", "xpack.transform.transformList.startedTransformToolTip": "{transformId} 已启动。", "xpack.transform.transformList.startModalTitle": "启动 {transformId}?", "xpack.transform.transformList.startTransformErrorMessage": "启动转换 {transformId} 时发生错误", - "xpack.transform.transformList.startTransformSuccessMessage": "启动转换 {transformId} 的请求已确认。", "xpack.transform.transformList.stopModalTitle": "停止 {transformId}?", "xpack.transform.transformList.stoppedTransformToolTip": "{transformId} 已停止。", "xpack.transform.transformList.stopTransformErrorMessage": "停止数据帧转换 {transformId} 时发生错误", - "xpack.transform.transformList.stopTransformSuccessMessage": "停止数据帧转换 {transformId} 的请求已确认。", "xpack.transform.transformList.unauthorizedTransformsCallout.insufficientPermissionsMsg": "{unauthorizedCnt, plural, other {已创建 # 个转换,但权限不足。}}", "xpack.transform.transformList.unauthorizedTransformsCallout.reauthorizeMsg": "重新授权以启动{unauthorizedCnt, plural, other {# 个转换}}。", "xpack.transform.transformNodes.noTransformNodesCallOutBody": "您将无法创建或运行转换。{learnMoreLink}", @@ -37803,9 +37785,6 @@ "xpack.transform.alertTypes.transformHealth.notStartedCheckName": "转换未启动", "xpack.transform.alertTypes.transformHealth.testsConfigTransforms.errorMessage": "必须至少选择一次运行状况检查", "xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel": "启用", - "xpack.transform.app.checkingPrivilegesDescription": "正在检查权限……", - "xpack.transform.app.checkingPrivilegesErrorMessage": "从服务器获取用户权限时出错", - "xpack.transform.app.deniedPrivilegeTitle": "您缺少集群权限", "xpack.transform.appName": "转换", "xpack.transform.appTitle": "转换", "xpack.transform.capability.noPermission.canCreateTransformAlertsTooltip": "您无权创建转换告警规则。", @@ -37851,7 +37830,6 @@ "xpack.transform.licenseCheckErrorMessage": "许可证检查失败", "xpack.transform.list.emptyPromptButtonText": "创建您的首个转换", "xpack.transform.list.emptyPromptTitle": "找不到转换", - "xpack.transform.list.errorPromptTitle": "获取转换列表时发生错误", "xpack.transform.mode": "模式", "xpack.transform.modeFilter": "模式", "xpack.transform.models.transformService.allOtherRequestsCancelledDescription": "所有其他请求已取消。", @@ -38083,7 +38061,6 @@ "xpack.transform.transformList.editFlyoutUpdateButtonText": "更新", "xpack.transform.transformList.editManagedTransformsDescription": "正在编辑", "xpack.transform.transformList.editTransformGenericErrorMessage": "调用用于更新转换的 API 终端时发生错误。", - "xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "检查用户是否可以删除目标索引时发生错误", "xpack.transform.transformList.managedBadgeLabel": "托管", "xpack.transform.transformList.managedBadgeTooltip": "此转换由 Elastic 预配置和管理;该产品的其他部分可能依赖于其行为。", "xpack.transform.transformList.needsReauthorizationBadge.contactAdminTooltip": "请联系管理员请求所需权限。", diff --git a/x-pack/test/api_integration/apis/transform/stop_transforms.ts b/x-pack/test/api_integration/apis/transform/stop_transforms.ts index 7811432e7417a..c982c6b45f1c9 100644 --- a/x-pack/test/api_integration/apis/transform/stop_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/stop_transforms.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import type { PutTransformsRequestSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; import type { StopTransformsRequestSchema } from '@kbn/transform-plugin/common/api_schemas/stop_transforms'; -import { isStopTransformsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; @@ -76,7 +75,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body[transformId].success).to.eql(true); expect(typeof body[transformId].error).to.eql('undefined'); await transform.api.waitForTransformState(transformId, TRANSFORM_STATE.STOPPED); @@ -97,7 +95,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body[transformId].success).to.eql(false); expect(typeof body[transformId].error).to.eql('object'); @@ -121,7 +118,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body.invalid_transform_id.success).to.eql(false); expect(body.invalid_transform_id).to.have.property('error'); }); @@ -158,8 +154,6 @@ export default ({ getService }: FtrProviderContext) => { .send(reqBody); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); - await asyncForEach(reqBody, async ({ id: transformId }: { id: string }, idx: number) => { expect(body[transformId].success).to.eql(true); await transform.api.waitForTransformState(transformId, TRANSFORM_STATE.STOPPED); @@ -183,8 +177,6 @@ export default ({ getService }: FtrProviderContext) => { ]); transform.api.assertResponseStatusCode(200, status, body); - expect(isStopTransformsResponseSchema(body)).to.eql(true); - await asyncForEach(reqBody, async ({ id: transformId }: { id: string }, idx: number) => { expect(body[transformId].success).to.eql(true); await transform.api.waitForTransformState(transformId, TRANSFORM_STATE.STOPPED); diff --git a/x-pack/test/api_integration/apis/transform/transforms.ts b/x-pack/test/api_integration/apis/transform/transforms.ts index 4c3d1641d6d5f..2016876ba93a6 100644 --- a/x-pack/test/api_integration/apis/transform/transforms.ts +++ b/x-pack/test/api_integration/apis/transform/transforms.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import type { GetTransformsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; -import { isGetTransformsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { getCommonRequestHeader } from '../../../functional/services/ml/common_api'; import { USER } from '../../../functional/services/transform/security_common'; @@ -43,8 +42,6 @@ export default ({ getService }: FtrProviderContext) => { } function assertTransformsResponseBody(body: GetTransformsResponseSchema) { - expect(isGetTransformsResponseSchema(body)).to.eql(true); - expect(body.count).to.eql(expected.apiTransformTransforms.count); expect(body.transforms).to.have.length(expected.apiTransformTransforms.count); @@ -62,8 +59,6 @@ export default ({ getService }: FtrProviderContext) => { } function assertSingleTransformResponseBody(body: GetTransformsResponseSchema) { - expect(isGetTransformsResponseSchema(body)).to.eql(true); - expect(body.count).to.eql(expected.apiTransformTransformsTransformId.count); expect(body.transforms).to.have.length(expected.apiTransformTransformsTransformId.count); diff --git a/x-pack/test/api_integration/apis/transform/transforms_nodes.ts b/x-pack/test/api_integration/apis/transform/transforms_nodes.ts index bf3e91df69684..10f4413a8f85f 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_nodes.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_nodes.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import type { GetTransformNodesResponseSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; -import { isGetTransformNodesResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { getCommonRequestHeader } from '../../../functional/services/ml/common_api'; import { USER } from '../../../functional/services/transform/security_common'; @@ -25,8 +24,6 @@ export default ({ getService }: FtrProviderContext) => { }; function assertTransformsNodesResponseBody(body: GetTransformNodesResponseSchema) { - expect(isGetTransformNodesResponseSchema(body)).to.eql(true); - expect(body.count).to.not.be.lessThan(expected.apiTransformTransformsNodes.minCount); } diff --git a/x-pack/test/api_integration/apis/transform/transforms_stats.ts b/x-pack/test/api_integration/apis/transform/transforms_stats.ts index 41cdafd0c058f..aea5c049343e9 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_stats.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_stats.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import type { GetTransformsStatsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/transforms_stats'; -import { isGetTransformsStatsResponseSchema } from '@kbn/transform-plugin/common/api_schemas/type_guards'; import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; import { getCommonRequestHeader } from '../../../functional/services/ml/common_api'; @@ -39,7 +38,6 @@ export default ({ getService }: FtrProviderContext) => { } function assertTransformsStatsResponseBody(body: GetTransformsStatsResponseSchema) { - expect(isGetTransformsStatsResponseSchema(body)).to.eql(true); expect(body.count).to.eql(expected.apiTransformTransforms.count); expect(body.transforms).to.have.length(expected.apiTransformTransforms.count);