mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[8.x] [ML] Add Spaces column to Anomaly Detection, Data Frame Analytics and Trained Models management pages (#206696) (#208652)
# Backport This will backport the following commits from `main` to `8.x`: - [[ML] Add Spaces column to Anomaly Detection, Data Frame Analytics and Trained Models management pages (#206696)](https://github.com/elastic/kibana/pull/206696) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Quynh Nguyen (Quinn)","email":"43350163+qn895@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-01-28T23:12:17Z","message":"[ML] Add Spaces column to Anomaly Detection, Data Frame Analytics and Trained Models management pages (#206696)\n\n## Summary\r\n\r\nThis PR adds a new `Spaces` column to Anomaly detection, Data Frame\r\nAnalytics, and Trained Models tables for management of spaces.\r\n\r\n\r\n\r\n85f9be3a
-a56e-4ba8-9bcf-06f2b8e01cf7\r\n\r\nIf user does not have permission to share to other spaces, the spaces\r\nbutton will be disabled completely.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/8c188240-9adf-439b-a6ea-5ad2b4c3ad0a\r\n\r\n**Reviewer's note:**\r\n\r\n- For kibana-security: A small change in the hook was updated to fix an\r\nerror with component set state after unmounting\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This was checked for breaking HTTP API changes, and any breaking\r\nchanges have been approved by the breaking-change committee. The\r\n`release_note:breaking` label should be applied in these situations.\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] The PR description includes the appropriate Release Notes section,\r\nand the correct `release_note:*` label is applied per the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n### Identify risks\r\n\r\nDoes this PR introduce any risks? For example, consider risks like hard\r\nto test bugs, performance regression, potential of data loss.\r\n\r\nDescribe the risk, its severity, and mitigation for each identified\r\nrisk. Invite stakeholders and evaluate how to proceed before merging.\r\n\r\n- [ ] [See some risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\r\n- [ ] ...\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"b5de0a7fc4a240c3d73c747b77768bbc401a5a14","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement",":ml","Feature:Anomaly Detection","Feature:Data Frame Analytics","v9.0.0","backport:version","v8.18.0"],"title":"[ML] Add Spaces column to Anomaly Detection, Data Frame Analytics and Trained Models management pages","number":206696,"url":"https://github.com/elastic/kibana/pull/206696","mergeCommit":{"message":"[ML] Add Spaces column to Anomaly Detection, Data Frame Analytics and Trained Models management pages (#206696)\n\n## Summary\r\n\r\nThis PR adds a new `Spaces` column to Anomaly detection, Data Frame\r\nAnalytics, and Trained Models tables for management of spaces.\r\n\r\n\r\n\r\n85f9be3a
-a56e-4ba8-9bcf-06f2b8e01cf7\r\n\r\nIf user does not have permission to share to other spaces, the spaces\r\nbutton will be disabled completely.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/8c188240-9adf-439b-a6ea-5ad2b4c3ad0a\r\n\r\n**Reviewer's note:**\r\n\r\n- For kibana-security: A small change in the hook was updated to fix an\r\nerror with component set state after unmounting\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This was checked for breaking HTTP API changes, and any breaking\r\nchanges have been approved by the breaking-change committee. The\r\n`release_note:breaking` label should be applied in these situations.\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] The PR description includes the appropriate Release Notes section,\r\nand the correct `release_note:*` label is applied per the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n### Identify risks\r\n\r\nDoes this PR introduce any risks? For example, consider risks like hard\r\nto test bugs, performance regression, potential of data loss.\r\n\r\nDescribe the risk, its severity, and mitigation for each identified\r\nrisk. Invite stakeholders and evaluate how to proceed before merging.\r\n\r\n- [ ] [See some risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\r\n- [ ] ...\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"b5de0a7fc4a240c3d73c747b77768bbc401a5a14"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/206696","number":206696,"mergeCommit":{"message":"[ML] Add Spaces column to Anomaly Detection, Data Frame Analytics and Trained Models management pages (#206696)\n\n## Summary\r\n\r\nThis PR adds a new `Spaces` column to Anomaly detection, Data Frame\r\nAnalytics, and Trained Models tables for management of spaces.\r\n\r\n\r\n\r\n85f9be3a
-a56e-4ba8-9bcf-06f2b8e01cf7\r\n\r\nIf user does not have permission to share to other spaces, the spaces\r\nbutton will be disabled completely.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/8c188240-9adf-439b-a6ea-5ad2b4c3ad0a\r\n\r\n**Reviewer's note:**\r\n\r\n- For kibana-security: A small change in the hook was updated to fix an\r\nerror with component set state after unmounting\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This was checked for breaking HTTP API changes, and any breaking\r\nchanges have been approved by the breaking-change committee. The\r\n`release_note:breaking` label should be applied in these situations.\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] The PR description includes the appropriate Release Notes section,\r\nand the correct `release_note:*` label is applied per the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n### Identify risks\r\n\r\nDoes this PR introduce any risks? For example, consider risks like hard\r\nto test bugs, performance regression, potential of data loss.\r\n\r\nDescribe the risk, its severity, and mitigation for each identified\r\nrisk. Invite stakeholders and evaluate how to proceed before merging.\r\n\r\n- [ ] [See some risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\r\n- [ ] ...\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"b5de0a7fc4a240c3d73c747b77768bbc401a5a14"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Quynh Nguyen (Quinn) <43350163+qn895@users.noreply.github.com>
This commit is contained in:
parent
2b0ccf33a4
commit
b65acc7c66
25 changed files with 561 additions and 309 deletions
|
@ -7,8 +7,11 @@
|
|||
|
||||
import type { ErrorType } from '@kbn/ml-error-utils';
|
||||
|
||||
export type JobType = 'anomaly-detector' | 'data-frame-analytics';
|
||||
export type TrainedModelType = 'trained-model';
|
||||
export const ANOMALY_DETECTOR_SAVED_OBJECT_TYPE = 'anomaly-detector';
|
||||
export const DFA_SAVED_OBJECT_TYPE = 'data-frame-analytics';
|
||||
export const TRAINED_MODEL_SAVED_OBJECT_TYPE = 'trained-model';
|
||||
export type JobType = typeof ANOMALY_DETECTOR_SAVED_OBJECT_TYPE | typeof DFA_SAVED_OBJECT_TYPE;
|
||||
export type TrainedModelType = typeof TRAINED_MODEL_SAVED_OBJECT_TYPE;
|
||||
export type MlSavedObjectType = JobType | TrainedModelType;
|
||||
|
||||
export const ML_JOB_SAVED_OBJECT_TYPE = 'ml-job';
|
||||
|
|
|
@ -324,6 +324,10 @@ interface BaseModelItem {
|
|||
* Indices with associated pipelines that have inference processors utilizing the model deployments.
|
||||
*/
|
||||
indices?: string[];
|
||||
/**
|
||||
* Spaces associated with the model
|
||||
*/
|
||||
spaces?: string[];
|
||||
}
|
||||
|
||||
/** Common properties for existing NLP models and NLP model download configs */
|
||||
|
|
|
@ -100,6 +100,7 @@ const App: FC<AppProps> = ({
|
|||
unifiedSearch: deps.unifiedSearch,
|
||||
usageCollection: deps.usageCollection,
|
||||
mlServices: getMlGlobalServices(coreStart, deps.data.dataViews, deps.usageCollection),
|
||||
spaces: deps.spaces,
|
||||
};
|
||||
}, [deps, coreStart]);
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 type { SpacesContextProps } from '@kbn/spaces-plugin/public';
|
||||
|
||||
export const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => (
|
||||
<>{children}</>
|
||||
);
|
|
@ -12,6 +12,7 @@ import { Redirect } from 'react-router-dom';
|
|||
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { EuiPageSection } from '@elastic/eui';
|
||||
import { map, distinctUntilChanged } from 'rxjs';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { type AppMountParameters } from '@kbn/core/public';
|
||||
|
@ -67,15 +68,19 @@ export const MlPage: FC<{ pageDeps: PageDependencies }> = React.memo(({ pageDeps
|
|||
const subscriptions = new Subscription();
|
||||
|
||||
subscriptions.add(
|
||||
httpService.getLoadingCount$.subscribe((v) => {
|
||||
setIsLoading(v !== 0);
|
||||
})
|
||||
httpService.getLoadingCount$
|
||||
.pipe(
|
||||
map((v) => v !== 0),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe((loading) => {
|
||||
setIsLoading(loading);
|
||||
})
|
||||
);
|
||||
return function cleanup() {
|
||||
subscriptions.unsubscribe();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [httpService?.getLoadingCount$]);
|
||||
|
||||
const routeList = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -18,10 +18,11 @@ import { useToastNotificationService } from '../../services/toast_notification_s
|
|||
|
||||
interface Props {
|
||||
spacesApi: SpacesPluginStart; // this component is only ever used when spaces is enabled
|
||||
spaceIds: string[];
|
||||
spaceIds?: string[];
|
||||
id: string;
|
||||
mlSavedObjectType: MlSavedObjectType;
|
||||
refresh(): void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const ALL_SPACES_ID = '*';
|
||||
|
@ -33,12 +34,14 @@ const modelObjectNoun = i18n.translate('xpack.ml.management.jobsSpacesList.model
|
|||
defaultMessage: 'trained model',
|
||||
});
|
||||
|
||||
const FALLBACK_SPACES_ID: string[] = [];
|
||||
export const MLSavedObjectsSpacesList: FC<Props> = ({
|
||||
spacesApi,
|
||||
spaceIds,
|
||||
spaceIds = FALLBACK_SPACES_ID,
|
||||
id,
|
||||
mlSavedObjectType,
|
||||
refresh,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const {
|
||||
savedObjects: { updateJobsSpaces, updateModelsSpaces },
|
||||
|
@ -107,6 +110,7 @@ export const MLSavedObjectsSpacesList: FC<Props> = ({
|
|||
return (
|
||||
<>
|
||||
<EuiButtonEmpty
|
||||
disabled={disabled}
|
||||
onClick={() => setShowFlyout(true)}
|
||||
style={{ height: 'auto' }}
|
||||
data-test-subj="mlJobListRowManageSpacesButton"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { SpaceManagementContextWrapper } from './spaces_management_context_wrapper';
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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, type PropsWithChildren } from 'react';
|
||||
import { useSpacesContextWrapper } from '../../hooks/use_spaces';
|
||||
|
||||
export const SpaceManagementContextWrapper: FC<PropsWithChildren<{ feature?: string }>> = ({
|
||||
children,
|
||||
feature,
|
||||
}) => {
|
||||
const ContextWrapper = useSpacesContextWrapper();
|
||||
|
||||
return <ContextWrapper feature={feature}>{children}</ContextWrapper>;
|
||||
};
|
|
@ -56,7 +56,7 @@ interface StartPlugins {
|
|||
savedSearch: SavedSearchPublicPluginStart;
|
||||
security?: SecurityPluginStart;
|
||||
share: SharePluginStart;
|
||||
spacesApi?: SpacesPluginStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
triggersActionsUi?: TriggersAndActionsUIPublicPluginStart;
|
||||
uiActions: UiActionsStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
|
|
|
@ -41,6 +41,7 @@ import { AnalyticsEmptyPrompt } from '../empty_prompt';
|
|||
import { useTableSettings } from './use_table_settings';
|
||||
import { JobsAwaitingNodeWarning } from '../../../../../components/jobs_awaiting_node_warning';
|
||||
import { useRefresh } from '../../../../../routing/use_refresh';
|
||||
import { SpaceManagementContextWrapper } from '../../../../../components/space_management_context_wrapper';
|
||||
|
||||
const filters: EuiSearchBarProps['filters'] = [
|
||||
{
|
||||
|
@ -259,43 +260,45 @@ export const DataFrameAnalyticsList: FC<Props> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div data-test-subj="mlAnalyticsJobList">
|
||||
{modals}
|
||||
<JobsAwaitingNodeWarning jobCount={jobsAwaitingNodeCount} />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{stats}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<CreateAnalyticsButton
|
||||
isDisabled={disabled}
|
||||
navigateToSourceSelection={navigateToSourceSelection}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<div data-test-subj="mlAnalyticsTableContainer">
|
||||
<EuiInMemoryTable<DataFrameAnalyticsListRow>
|
||||
rowHeader={DataFrameAnalyticsListColumn.id}
|
||||
allowNeutralSort={false}
|
||||
columns={columns}
|
||||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
items={analytics}
|
||||
itemId={DataFrameAnalyticsListColumn.id}
|
||||
loading={isLoading}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
search={search}
|
||||
data-test-subj={isLoading ? 'mlAnalyticsTable loading' : 'mlAnalyticsTable loaded'}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `mlAnalyticsTableRow row-${item.id}`,
|
||||
})}
|
||||
error={searchError}
|
||||
/>
|
||||
<SpaceManagementContextWrapper>
|
||||
<div data-test-subj="mlAnalyticsJobList">
|
||||
{modals}
|
||||
<JobsAwaitingNodeWarning jobCount={jobsAwaitingNodeCount} />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{stats}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<CreateAnalyticsButton
|
||||
isDisabled={disabled}
|
||||
navigateToSourceSelection={navigateToSourceSelection}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<div data-test-subj="mlAnalyticsTableContainer">
|
||||
<EuiInMemoryTable<DataFrameAnalyticsListRow>
|
||||
rowHeader={DataFrameAnalyticsListColumn.id}
|
||||
allowNeutralSort={false}
|
||||
columns={columns}
|
||||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
items={analytics}
|
||||
itemId={DataFrameAnalyticsListColumn.id}
|
||||
loading={isLoading}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
search={search}
|
||||
data-test-subj={isLoading ? 'mlAnalyticsTable loading' : 'mlAnalyticsTable loaded'}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `mlAnalyticsTableRow row-${item.id}`,
|
||||
})}
|
||||
error={searchError}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SpaceManagementContextWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -94,6 +94,7 @@ export interface DataFrameAnalyticsListRow {
|
|||
mode: string;
|
||||
state: DataFrameAnalyticsStats['state'];
|
||||
stats: DataFrameAnalyticsStats;
|
||||
spaces?: string[];
|
||||
}
|
||||
|
||||
// Used to pass on attribute names to table columns
|
||||
|
|
|
@ -34,8 +34,11 @@ import {
|
|||
DataFrameAnalyticsListColumn,
|
||||
} from './common';
|
||||
import { useActions } from './use_actions';
|
||||
import { useMlLink } from '../../../../../contexts/kibana';
|
||||
import { useMlLink, useMlKibana } from '../../../../../contexts/kibana';
|
||||
import { ML_PAGES } from '../../../../../../../common/constants/locator';
|
||||
import { MLSavedObjectsSpacesList } from '../../../../../components/ml_saved_objects_spaces_list';
|
||||
import { DFA_SAVED_OBJECT_TYPE } from '../../../../../../../common/types/saved_objects';
|
||||
import { useCanManageSpacesAndSavedObjects } from '../../../../../hooks/use_spaces';
|
||||
|
||||
const TRUNCATE_TEXT_LINES = 3;
|
||||
|
||||
|
@ -164,6 +167,9 @@ export const useColumns = (
|
|||
isMlEnabledInSpace: boolean = true,
|
||||
refresh: () => void = () => {}
|
||||
) => {
|
||||
const {
|
||||
services: { spaces, application },
|
||||
} = useMlKibana();
|
||||
const { actions, modals } = useActions();
|
||||
function toggleDetails(item: DataFrameAnalyticsListRow) {
|
||||
const index = expandedRowItemIds.indexOf(item.config.id);
|
||||
|
@ -177,6 +183,12 @@ export const useColumns = (
|
|||
// spread to a new array otherwise the component wouldn't re-render
|
||||
setExpandedRowItemIds([...expandedRowItemIds]);
|
||||
}
|
||||
|
||||
const canManageSpacesAndSavedObjects = useCanManageSpacesAndSavedObjects();
|
||||
const shouldDisableSpacesColumn =
|
||||
!canManageSpacesAndSavedObjects ||
|
||||
!application.capabilities.savedObjectsManagement?.shareIntoSpace;
|
||||
|
||||
// update possible column types to something like (FieldDataColumn | ComputedColumn | ActionsColumn)[] when they have been added to EUI
|
||||
const columns: any[] = [
|
||||
{
|
||||
|
@ -283,6 +295,33 @@ export const useColumns = (
|
|||
'data-test-subj': 'mlAnalyticsTableColumnStatus',
|
||||
},
|
||||
progressColumn,
|
||||
...(canManageSpacesAndSavedObjects && spaces
|
||||
? [
|
||||
{
|
||||
name: i18n.translate('xpack.ml.jobsList.jobActionsColumn.spaces', {
|
||||
defaultMessage: 'Spaces',
|
||||
}),
|
||||
'data-test-subj': 'mlTableColumnSpaces',
|
||||
truncateText: true,
|
||||
align: 'right',
|
||||
width: '10%',
|
||||
disabled: shouldDisableSpacesColumn,
|
||||
render: (item: DataFrameAnalyticsListRow) => {
|
||||
return (
|
||||
<MLSavedObjectsSpacesList
|
||||
disabled={shouldDisableSpacesColumn}
|
||||
spacesApi={spaces}
|
||||
spaceIds={item.spaces}
|
||||
id={item.id}
|
||||
mlSavedObjectType={DFA_SAVED_OBJECT_TYPE}
|
||||
refresh={refresh}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
name: i18n.translate('xpack.ml.dataframe.analyticsList.tableActionLabel', {
|
||||
defaultMessage: 'Actions',
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
type DataFrameAnalysisConfigType,
|
||||
DATA_FRAME_TASK_STATE,
|
||||
} from '@kbn/ml-data-frame-analytics-utils';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { useMlApi } from '../../../../../contexts/kibana';
|
||||
import type {
|
||||
GetDataFrameAnalyticsStatsResponseError,
|
||||
|
@ -27,6 +28,8 @@ import {
|
|||
isDataFrameAnalyticsStopped,
|
||||
} from '../../components/analytics_list/common';
|
||||
import type { AnalyticStatsBarStats } from '../../../../../components/stats_bar';
|
||||
import { DFA_SAVED_OBJECT_TYPE } from '../../../../../../../common/types/saved_objects';
|
||||
import { useCanManageSpacesAndSavedObjects } from '../../../../../hooks/use_spaces';
|
||||
|
||||
export const isGetDataFrameAnalyticsStatsResponseOk = (
|
||||
arg: any
|
||||
|
@ -117,6 +120,7 @@ export const useGetAnalytics = (
|
|||
blockRefresh: boolean
|
||||
): GetAnalytics => {
|
||||
const mlApi = useMlApi();
|
||||
const canManageSpacesAndSavedObjects = useCanManageSpacesAndSavedObjects();
|
||||
|
||||
let concurrentLoads = 0;
|
||||
|
||||
|
@ -133,6 +137,13 @@ export const useGetAnalytics = (
|
|||
const analyticsConfigs = await mlApi.dataFrameAnalytics.getDataFrameAnalytics();
|
||||
const analyticsStats = await mlApi.dataFrameAnalytics.getDataFrameAnalyticsStats();
|
||||
|
||||
let savedObjectsSpaces: Record<string, string[]> = {};
|
||||
if (canManageSpacesAndSavedObjects && mlApi.savedObjects.jobsSpaces) {
|
||||
const results = await mlApi.savedObjects.jobsSpaces();
|
||||
if (isPopulatedObject(results, [DFA_SAVED_OBJECT_TYPE])) {
|
||||
savedObjectsSpaces = results[DFA_SAVED_OBJECT_TYPE];
|
||||
}
|
||||
}
|
||||
const analyticsStatsResult = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
|
||||
? getAnalyticsJobsStats(analyticsStats)
|
||||
: undefined;
|
||||
|
@ -164,6 +175,7 @@ export const useGetAnalytics = (
|
|||
mode: DATA_FRAME_MODE.BATCH,
|
||||
state: stats.state,
|
||||
stats,
|
||||
spaces: savedObjectsSpaces[config.id],
|
||||
});
|
||||
return reducedtableRows;
|
||||
},
|
||||
|
|
|
@ -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 { useMemo } from 'react';
|
||||
import { useMlKibana } from '../contexts/kibana';
|
||||
import { getEmptyFunctionComponent } from '../components/empty_component/get_empty_function_component';
|
||||
|
||||
export const useCanManageSpacesAndSavedObjects = () => {
|
||||
const {
|
||||
services: { spaces },
|
||||
} = useMlKibana();
|
||||
const canManageSpacesAndSavedObjects = useMemo(() => spaces !== undefined, [spaces]);
|
||||
|
||||
return canManageSpacesAndSavedObjects;
|
||||
};
|
||||
|
||||
export const useSpacesContextWrapper = () => {
|
||||
const {
|
||||
services: { spaces },
|
||||
} = useMlKibana();
|
||||
|
||||
return useMemo(
|
||||
() => (spaces ? spaces.ui.components.getSpacesContextProvider : getEmptyFunctionComponent),
|
||||
[spaces]
|
||||
);
|
||||
};
|
|
@ -32,6 +32,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { AnomalyDetectionJobIdLink } from './job_id_link';
|
||||
import { isManagedJob } from '../../../jobs_utils';
|
||||
import { MLSavedObjectsSpacesList } from '../../../../components/ml_saved_objects_spaces_list';
|
||||
import { ANOMALY_DETECTOR_SAVED_OBJECT_TYPE } from '../../../../../../common/types/saved_objects';
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [10, 25, 50];
|
||||
|
||||
|
@ -327,6 +329,35 @@ export class JobsListUI extends Component {
|
|||
render: (item) => <ResultLinks jobs={[item]} />,
|
||||
width: '64px',
|
||||
},
|
||||
...(this.props.kibana.services.spaces
|
||||
? [
|
||||
{
|
||||
name: i18n.translate('xpack.ml.jobsList.jobActionsColumn.spaces', {
|
||||
defaultMessage: 'Spaces',
|
||||
}),
|
||||
'data-test-subj': 'mlTableColumnSpaces',
|
||||
truncateText: true,
|
||||
align: 'right',
|
||||
width: '10%',
|
||||
render: (item) => {
|
||||
return (
|
||||
<MLSavedObjectsSpacesList
|
||||
disabled={
|
||||
!this.props.kibana.services.application?.capabilities?.savedObjectsManagement
|
||||
?.shareIntoSpace
|
||||
}
|
||||
spacesApi={this.props.kibana.services.spaces}
|
||||
spaceIds={item.spaces}
|
||||
id={item.id}
|
||||
mlSavedObjectType={ANOMALY_DETECTOR_SAVED_OBJECT_TYPE}
|
||||
refresh={this.props.refreshJobs}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
name: i18n.translate('xpack.ml.jobsList.actionsLabel', {
|
||||
defaultMessage: 'Actions',
|
||||
|
@ -375,33 +406,35 @@ export class JobsListUI extends Component {
|
|||
const selectedJobsClass = this.props.selectedJobsCount ? 'jobs-selected' : '';
|
||||
|
||||
return (
|
||||
<EuiBasicTable
|
||||
data-test-subj={loading ? 'mlJobListTable loading' : 'mlJobListTable loaded'}
|
||||
loading={loading === true}
|
||||
noItemsMessage={
|
||||
loading
|
||||
? i18n.translate('xpack.ml.jobsList.loadingJobsLabel', {
|
||||
defaultMessage: 'Loading jobs…',
|
||||
})
|
||||
: i18n.translate('xpack.ml.jobsList.noJobsFoundLabel', {
|
||||
defaultMessage: 'No jobs found',
|
||||
})
|
||||
}
|
||||
itemId="id"
|
||||
className={`jobs-list-table ${selectedJobsClass}`}
|
||||
items={pageOfItems}
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
onChange={this.onTableChange}
|
||||
selection={selectionControls}
|
||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||
sorting={sorting}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `mlJobListRow row-${item.id}`,
|
||||
})}
|
||||
css={{ '.euiTableRow-isExpandedRow .euiTableCellContent': { animation: 'none' } }}
|
||||
rowHeader="id"
|
||||
/>
|
||||
<>
|
||||
<EuiBasicTable
|
||||
data-test-subj={loading ? 'mlJobListTable loading' : 'mlJobListTable loaded'}
|
||||
loading={loading === true}
|
||||
noItemsMessage={
|
||||
loading
|
||||
? i18n.translate('xpack.ml.jobsList.loadingJobsLabel', {
|
||||
defaultMessage: 'Loading jobs…',
|
||||
})
|
||||
: i18n.translate('xpack.ml.jobsList.noJobsFoundLabel', {
|
||||
defaultMessage: 'No jobs found',
|
||||
})
|
||||
}
|
||||
itemId="id"
|
||||
className={`jobs-list-table ${selectedJobsClass}`}
|
||||
items={pageOfItems}
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
onChange={this.onTableChange}
|
||||
selection={selectionControls}
|
||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||
sorting={sorting}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `mlJobListRow row-${item.id}`,
|
||||
})}
|
||||
css={{ '.euiTableRow-isExpandedRow .euiTableCellContent': { animation: 'none' } }}
|
||||
rowHeader="id"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ import { CloseJobsConfirmModal } from '../confirm_modals/close_jobs_confirm_moda
|
|||
import { AnomalyDetectionEmptyState } from '../anomaly_detection_empty_state';
|
||||
import { removeNodeInfo } from '../../../../../../common/util/job_utils';
|
||||
import { jobCloningService } from '../../../../services/job_cloning_service';
|
||||
import { ANOMALY_DETECTOR_SAVED_OBJECT_TYPE } from '../../../../../../common/types/saved_objects';
|
||||
import { SpaceManagementContextWrapper } from '../../../../components/space_management_context_wrapper';
|
||||
|
||||
let blockingJobsRefreshTimeout = null;
|
||||
|
||||
|
@ -322,7 +324,10 @@ export class JobsListViewUI extends Component {
|
|||
const expandedJobsIds = Object.keys(this.state.itemIdToExpandedRowMap);
|
||||
try {
|
||||
let jobsAwaitingNodeCount = 0;
|
||||
const jobs = await mlApi.jobs.jobsSummary(expandedJobsIds);
|
||||
const [jobs, jobsSpaces] = await Promise.all([
|
||||
mlApi.jobs.jobsSummary(expandedJobsIds),
|
||||
mlApi.savedObjects.jobsSpaces(),
|
||||
]);
|
||||
const fullJobsList = {};
|
||||
const jobsSummaryList = jobs.map((job) => {
|
||||
if (job.fullJob !== undefined) {
|
||||
|
@ -332,6 +337,13 @@ export class JobsListViewUI extends Component {
|
|||
fullJobsList[job.id] = job.fullJob;
|
||||
delete job.fullJob;
|
||||
}
|
||||
if (
|
||||
jobsSpaces &&
|
||||
jobsSpaces[ANOMALY_DETECTOR_SAVED_OBJECT_TYPE] &&
|
||||
jobsSpaces[ANOMALY_DETECTOR_SAVED_OBJECT_TYPE][job.id]
|
||||
) {
|
||||
job.spaces = jobsSpaces[ANOMALY_DETECTOR_SAVED_OBJECT_TYPE][job.id];
|
||||
}
|
||||
job.latestTimestampSortValue = job.latestTimestampMs || 0;
|
||||
|
||||
if (job.awaitingNodeAssignment === true) {
|
||||
|
@ -434,75 +446,77 @@ export class JobsListViewUI extends Component {
|
|||
<UpgradeWarning />
|
||||
|
||||
<>
|
||||
{noJobsFound ? <AnomalyDetectionEmptyState /> : null}
|
||||
<SpaceManagementContextWrapper>
|
||||
{noJobsFound ? <AnomalyDetectionEmptyState /> : null}
|
||||
|
||||
{jobIds.length > 0 ? (
|
||||
<>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<JobStatsBar
|
||||
jobsSummaryList={jobsSummaryList}
|
||||
showNodeInfo={this.props.showNodeInfo}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<NewJobButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<div>
|
||||
<EuiFlexGroup
|
||||
css={{
|
||||
alignItems: 'center',
|
||||
minHeight: '60px',
|
||||
}}
|
||||
gutterSize="none"
|
||||
>
|
||||
{jobIds.length > 0 ? (
|
||||
<>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<MultiJobActions
|
||||
selectedJobs={this.state.selectedJobs}
|
||||
allJobIds={jobIds}
|
||||
showCloseJobsConfirmModal={this.showCloseJobsConfirmModal}
|
||||
showStartDatafeedModal={this.showStartDatafeedModal}
|
||||
showDeleteJobModal={this.showDeleteJobModal}
|
||||
showResetJobModal={this.showResetJobModal}
|
||||
showCreateAlertFlyout={this.showCreateAlertFlyout}
|
||||
showStopDatafeedsConfirmModal={this.showStopDatafeedsConfirmModal}
|
||||
refreshJobs={() => this.refreshJobSummaryList()}
|
||||
<JobStatsBar
|
||||
jobsSummaryList={jobsSummaryList}
|
||||
showNodeInfo={this.props.showNodeInfo}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<JobFilterBar
|
||||
setFilters={this.setFilters}
|
||||
queryText={this.props.jobsViewState.queryText}
|
||||
/>
|
||||
<EuiFlexItem grow={false}>
|
||||
<NewJobButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<JobsList
|
||||
jobsSummaryList={this.state.filteredJobsSummaryList}
|
||||
fullJobsList={this.state.fullJobsList}
|
||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||
toggleRow={this.toggleRow}
|
||||
selectJobChange={this.selectJobChange}
|
||||
showEditJobFlyout={this.showEditJobFlyout}
|
||||
showDatafeedChartFlyout={this.showDatafeedChartFlyout}
|
||||
showDeleteJobModal={this.showDeleteJobModal}
|
||||
showResetJobModal={this.showResetJobModal}
|
||||
showCloseJobsConfirmModal={this.showCloseJobsConfirmModal}
|
||||
showStartDatafeedModal={this.showStartDatafeedModal}
|
||||
showStopDatafeedsConfirmModal={this.showStopDatafeedsConfirmModal}
|
||||
refreshJobs={() => this.refreshJobSummaryList()}
|
||||
jobsViewState={this.props.jobsViewState}
|
||||
onJobsViewStateUpdate={this.props.onJobsViewStateUpdate}
|
||||
selectedJobsCount={this.state.selectedJobs.length}
|
||||
showCreateAlertFlyout={this.showCreateAlertFlyout}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<div>
|
||||
<EuiFlexGroup
|
||||
css={{
|
||||
alignItems: 'center',
|
||||
minHeight: '60px',
|
||||
}}
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<MultiJobActions
|
||||
selectedJobs={this.state.selectedJobs}
|
||||
allJobIds={jobIds}
|
||||
showCloseJobsConfirmModal={this.showCloseJobsConfirmModal}
|
||||
showStartDatafeedModal={this.showStartDatafeedModal}
|
||||
showDeleteJobModal={this.showDeleteJobModal}
|
||||
showResetJobModal={this.showResetJobModal}
|
||||
showCreateAlertFlyout={this.showCreateAlertFlyout}
|
||||
showStopDatafeedsConfirmModal={this.showStopDatafeedsConfirmModal}
|
||||
refreshJobs={() => this.refreshJobSummaryList()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<JobFilterBar
|
||||
setFilters={this.setFilters}
|
||||
queryText={this.props.jobsViewState.queryText}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<JobsList
|
||||
jobsSummaryList={this.state.filteredJobsSummaryList}
|
||||
fullJobsList={this.state.fullJobsList}
|
||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||
toggleRow={this.toggleRow}
|
||||
selectJobChange={this.selectJobChange}
|
||||
showEditJobFlyout={this.showEditJobFlyout}
|
||||
showDatafeedChartFlyout={this.showDatafeedChartFlyout}
|
||||
showDeleteJobModal={this.showDeleteJobModal}
|
||||
showResetJobModal={this.showResetJobModal}
|
||||
showCloseJobsConfirmModal={this.showCloseJobsConfirmModal}
|
||||
showStartDatafeedModal={this.showStartDatafeedModal}
|
||||
showStopDatafeedsConfirmModal={this.showStopDatafeedsConfirmModal}
|
||||
refreshJobs={() => this.refreshJobSummaryList()}
|
||||
jobsViewState={this.props.jobsViewState}
|
||||
onJobsViewStateUpdate={this.props.onJobsViewStateUpdate}
|
||||
selectedJobsCount={this.state.selectedJobs.length}
|
||||
showCreateAlertFlyout={this.showCreateAlertFlyout}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</SpaceManagementContextWrapper>
|
||||
|
||||
<EditJobFlyout
|
||||
setShowFunction={this.setShowEditJobFlyoutFunction}
|
||||
|
|
|
@ -47,6 +47,7 @@ export const JobsPage: FC<JobsPageProps> = ({ isMlEnabledInSpace, lastRefresh })
|
|||
|
||||
const { showNodeInfo } = useEnabledFeatures();
|
||||
const helpLink = docLinks.links.ml.anomalyDetection;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MlPageHeader>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { FC } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Router } from '@kbn/shared-ux-router';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
||||
|
@ -27,8 +27,9 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
|||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import { SpaceManagementContextWrapper } from '../../../../components/space_management_context_wrapper';
|
||||
import { UpgradeWarning } from '../../../../components/upgrade/upgrade_warning';
|
||||
import { getMlGlobalServices } from '../../../../util/get_services';
|
||||
import { EnabledFeaturesContextProvider } from '../../../../contexts/ml';
|
||||
|
@ -45,13 +46,11 @@ import type { MlSavedObjectType } from '../../../../../../common/types/saved_obj
|
|||
import { SpaceManagement } from './space_management';
|
||||
import { DocsLink } from './docs_link';
|
||||
|
||||
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
|
||||
|
||||
interface Props {
|
||||
coreStart: CoreStart;
|
||||
share: SharePluginStart;
|
||||
history: ManagementAppMountParams['history'];
|
||||
spacesApi?: SpacesPluginStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
|
@ -63,7 +62,7 @@ export const JobsListPage: FC<Props> = ({
|
|||
coreStart,
|
||||
share,
|
||||
history,
|
||||
spacesApi,
|
||||
spaces,
|
||||
data,
|
||||
usageCollection,
|
||||
fieldFormats,
|
||||
|
@ -104,12 +103,6 @@ export const JobsListPage: FC<Props> = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const ContextWrapper = useCallback(
|
||||
spacesApi ? spacesApi.ui.components.getSpacesContextProvider : getEmptyFunctionComponent,
|
||||
[spacesApi]
|
||||
);
|
||||
|
||||
if (initialized === false) {
|
||||
return null;
|
||||
}
|
||||
|
@ -132,7 +125,7 @@ export const JobsListPage: FC<Props> = ({
|
|||
data,
|
||||
usageCollection,
|
||||
fieldFormats,
|
||||
spacesApi,
|
||||
spaces,
|
||||
mlServices,
|
||||
}}
|
||||
>
|
||||
|
@ -178,11 +171,11 @@ export const JobsListPage: FC<Props> = ({
|
|||
data,
|
||||
usageCollection,
|
||||
fieldFormats,
|
||||
spacesApi,
|
||||
spaces,
|
||||
mlServices,
|
||||
}}
|
||||
>
|
||||
<ContextWrapper feature={PLUGIN_ID}>
|
||||
<SpaceManagementContextWrapper feature={PLUGIN_ID}>
|
||||
<EnabledFeaturesContextProvider isServerless={isServerless} mlFeatures={mlFeatures}>
|
||||
<Router history={history}>
|
||||
<EuiPageTemplate.Header
|
||||
|
@ -238,14 +231,14 @@ export const JobsListPage: FC<Props> = ({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<SpaceManagement
|
||||
spacesApi={spacesApi}
|
||||
spacesApi={spaces}
|
||||
onTabChange={setCurrentTabId}
|
||||
onReload={setRefreshJobs}
|
||||
/>
|
||||
</EuiPageTemplate.Section>
|
||||
</Router>
|
||||
</EnabledFeaturesContextProvider>
|
||||
</ContextWrapper>
|
||||
</SpaceManagementContextWrapper>
|
||||
</KibanaContextProvider>
|
||||
</RedirectAppLinks>
|
||||
</KibanaRenderContextProvider>
|
||||
|
|
|
@ -31,6 +31,7 @@ import { useManagementApiService } from '../../../../../services/ml_api_service/
|
|||
import { getColumns } from './columns';
|
||||
import { MLSavedObjectsSpacesList } from '../../../../../components/ml_saved_objects_spaces_list';
|
||||
import { getFilters } from './filters';
|
||||
import { useMlKibana } from '../../../../../contexts/kibana/kibana_context';
|
||||
|
||||
interface Props {
|
||||
spacesApi?: SpacesPluginStart;
|
||||
|
@ -39,6 +40,9 @@ interface Props {
|
|||
}
|
||||
|
||||
export const SpaceManagement: FC<Props> = ({ spacesApi, onTabChange, onReload }) => {
|
||||
const {
|
||||
services: { application },
|
||||
} = useMlKibana();
|
||||
const { getList } = useManagementApiService();
|
||||
|
||||
const [currentTabId, setCurrentTabId] = useState<MlSavedObjectType | null>(null);
|
||||
|
@ -123,6 +127,9 @@ export const SpaceManagement: FC<Props> = ({ spacesApi, onTabChange, onReload })
|
|||
[currentTabId]
|
||||
);
|
||||
|
||||
const canShareIntoSpace = useMemo(() => {
|
||||
return !application?.capabilities?.savedObjectsManagement?.shareIntoSpace;
|
||||
}, [application]);
|
||||
const createColumns = useCallback(() => {
|
||||
if (currentTabId === null) {
|
||||
return [];
|
||||
|
@ -143,10 +150,11 @@ export const SpaceManagement: FC<Props> = ({ spacesApi, onTabChange, onReload })
|
|||
render: (item: ManagementItems) => {
|
||||
return (
|
||||
<MLSavedObjectsSpacesList
|
||||
disabled={canShareIntoSpace}
|
||||
spacesApi={spacesApi}
|
||||
spaceIds={item.spaces ?? []}
|
||||
id={item.id}
|
||||
spaceIds={item.spaces}
|
||||
mlSavedObjectType={currentTabId}
|
||||
id={item.id}
|
||||
refresh={refresh.bind(null, currentTabId)}
|
||||
/>
|
||||
);
|
||||
|
@ -155,7 +163,7 @@ export const SpaceManagement: FC<Props> = ({ spacesApi, onTabChange, onReload })
|
|||
]
|
||||
: []),
|
||||
] as Array<EuiBasicTableColumn<ManagementItems>>;
|
||||
}, [currentTabId, spacesApi, refresh]);
|
||||
}, [currentTabId, spacesApi, refresh, canShareIntoSpace]);
|
||||
|
||||
const getTable = useCallback(() => {
|
||||
return (
|
||||
|
|
|
@ -28,7 +28,7 @@ const renderApp = (
|
|||
fieldFormats: FieldFormatsStart,
|
||||
isServerless: boolean,
|
||||
mlFeatures: MlFeatures,
|
||||
spacesApi?: SpacesPluginStart,
|
||||
spaces?: SpacesPluginStart,
|
||||
usageCollection?: UsageCollectionSetup
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
|
@ -37,7 +37,7 @@ const renderApp = (
|
|||
history,
|
||||
share,
|
||||
data,
|
||||
spacesApi,
|
||||
spaces,
|
||||
usageCollection,
|
||||
fieldFormats,
|
||||
isServerless,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SearchFilterConfig } from '@elastic/eui';
|
||||
import type { HorizontalAlignment, SearchFilterConfig } from '@elastic/eui';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiButton,
|
||||
|
@ -70,6 +70,11 @@ import { getModelStateColor } from './get_model_state';
|
|||
import { useModelActions } from './model_actions';
|
||||
import { TestDfaModelsFlyout } from './test_dfa_models_flyout';
|
||||
import { TestModelAndPipelineCreationFlyout } from './test_models';
|
||||
import { MLSavedObjectsSpacesList } from '../components/ml_saved_objects_spaces_list';
|
||||
import { useCanManageSpacesAndSavedObjects } from '../hooks/use_spaces';
|
||||
import { useSavedObjectsApiService } from '../services/ml_api_service/saved_objects';
|
||||
import { TRAINED_MODEL_SAVED_OBJECT_TYPE } from '../../../common/types/saved_objects';
|
||||
import { SpaceManagementContextWrapper } from '../components/space_management_context_wrapper';
|
||||
|
||||
interface PageUrlState {
|
||||
pageKey: typeof ML_PAGES.TRAINED_MODELS_MANAGE;
|
||||
|
@ -111,11 +116,13 @@ export const ModelsList: FC<Props> = ({
|
|||
|
||||
const {
|
||||
services: {
|
||||
spaces,
|
||||
application: { capabilities },
|
||||
docLinks,
|
||||
},
|
||||
} = useMlKibana();
|
||||
|
||||
const savedObjectsApiService = useSavedObjectsApiService();
|
||||
const nlpElserDocUrl = docLinks.links.ml.nlpElser;
|
||||
|
||||
const { isNLPEnabled } = useEnabledFeatures();
|
||||
|
@ -173,7 +180,20 @@ export const ModelsList: FC<Props> = ({
|
|||
const fetchModelsData = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const resultItems = await trainedModelsApiService.getTrainedModelsList();
|
||||
const [trainedModelsResult, trainedModelsSpacesResult] = await Promise.allSettled([
|
||||
trainedModelsApiService.getTrainedModelsList(),
|
||||
canManageSpacesAndSavedObjects
|
||||
? savedObjectsApiService.trainedModelsSpaces()
|
||||
: ({} as Record<string, Record<string, string[]>>),
|
||||
]);
|
||||
|
||||
const resultItems =
|
||||
trainedModelsResult.status === 'fulfilled' ? trainedModelsResult.value : [];
|
||||
const trainedModelsSpaces =
|
||||
trainedModelsSpacesResult.status === 'fulfilled' ? trainedModelsSpacesResult.value : {};
|
||||
|
||||
const trainedModelsSavedObjects: Record<string, string[]> =
|
||||
trainedModelsSpaces?.trainedModels ?? {};
|
||||
|
||||
setItems((prevItems) => {
|
||||
// Need to merge existing items with new items
|
||||
|
@ -182,6 +202,7 @@ export const ModelsList: FC<Props> = ({
|
|||
const prevItem = prevItems.find((i) => i.model_id === item.model_id);
|
||||
return {
|
||||
...item,
|
||||
spaces: trainedModelsSavedObjects[item.model_id],
|
||||
...(isBaseNLPModelItem(prevItem) && prevItem?.state === MODEL_STATE.DOWNLOADING
|
||||
? {
|
||||
state: prevItem.state,
|
||||
|
@ -381,6 +402,9 @@ export const ModelsList: FC<Props> = ({
|
|||
modelAndDeploymentIds,
|
||||
onModelDownloadRequest,
|
||||
});
|
||||
const canManageSpacesAndSavedObjects = useCanManageSpacesAndSavedObjects();
|
||||
const shouldDisableSpacesColumn =
|
||||
!canManageSpacesAndSavedObjects || !capabilities.savedObjectsManagement?.shareIntoSpace;
|
||||
|
||||
const toggleDetails = async (item: TrainedModelUIItem) => {
|
||||
const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
|
||||
|
@ -555,6 +579,31 @@ export const ModelsList: FC<Props> = ({
|
|||
},
|
||||
'data-test-subj': 'mlModelsTableColumnDeploymentState',
|
||||
},
|
||||
...(canManageSpacesAndSavedObjects && spaces
|
||||
? [
|
||||
{
|
||||
name: i18n.translate('xpack.ml.jobsList.jobActionsColumn.spaces', {
|
||||
defaultMessage: 'Spaces',
|
||||
}),
|
||||
'data-test-subj': 'mlTableColumnSpaces',
|
||||
truncateText: true,
|
||||
align: 'right' as HorizontalAlignment,
|
||||
width: '10%',
|
||||
render: (item: TrainedModelUIItem) => {
|
||||
return (
|
||||
<MLSavedObjectsSpacesList
|
||||
disabled={shouldDisableSpacesColumn}
|
||||
spacesApi={spaces}
|
||||
spaceIds={item.spaces}
|
||||
id={item.model_id}
|
||||
mlSavedObjectType={TRAINED_MODEL_SAVED_OBJECT_TYPE}
|
||||
refresh={fetchModelsData}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: i18n.translate('xpack.ml.trainedModels.modelsList.actionsHeader', {
|
||||
defaultMessage: 'Actions',
|
||||
|
@ -688,155 +737,160 @@ export const ModelsList: FC<Props> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<SavedObjectsWarning onCloseFlyout={fetchModelsData} forceRefresh={isLoading} />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{modelsStats ? (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatsBar stats={modelsStats} dataTestSub={'mlInferenceModelsStatsBar'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.showAllLabel"
|
||||
defaultMessage="Show all"
|
||||
/>
|
||||
}
|
||||
checked={!!pageState.showAll}
|
||||
onChange={(e) => updatePageState({ showAll: e.target.checked })}
|
||||
data-test-subj="mlModelsShowAllSwitch"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType={'plusInCircle'}
|
||||
color={'primary'}
|
||||
onClick={setIsAddModelFlyoutVisible.bind(null, true)}
|
||||
data-test-subj="mlModelsAddTrainedModelButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.addModelButtonLabel"
|
||||
defaultMessage="Add trained model"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<div data-test-subj="mlModelsTableContainer">
|
||||
<EuiInMemoryTable<TrainedModelUIItem>
|
||||
tableLayout={'auto'}
|
||||
responsiveBreakpoint={'xl'}
|
||||
allowNeutralSort={false}
|
||||
columns={columns}
|
||||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
items={tableItems}
|
||||
itemId={ModelsTableToConfigMapping.id}
|
||||
loading={isLoading}
|
||||
search={search}
|
||||
selection={selection}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `mlModelsTableRow row-${item.model_id}`,
|
||||
})}
|
||||
pagination={pagination}
|
||||
onTableChange={onTableChange}
|
||||
sorting={sorting}
|
||||
data-test-subj={isLoading ? 'mlModelsTable loading' : 'mlModelsTable loaded'}
|
||||
childrenBetween={
|
||||
isElserCalloutVisible ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.newElserModelTitle"
|
||||
defaultMessage="New ELSER model now available"
|
||||
/>
|
||||
}
|
||||
onDismiss={setIsElserCalloutDismissed.bind(null, true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.newElserModelDescription"
|
||||
defaultMessage="A new version of ELSER that shows faster performance and improved relevance is now available. {docLink} for information on how to start using it."
|
||||
values={{
|
||||
docLink: (
|
||||
<EuiLink href={nlpElserDocUrl} external target={'_blank'}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.startDeployment.viewElserDocLink"
|
||||
defaultMessage="View documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
<SpaceManagementContextWrapper>
|
||||
<SavedObjectsWarning onCloseFlyout={fetchModelsData} forceRefresh={isLoading} />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{modelsStats ? (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatsBar stats={modelsStats} dataTestSub={'mlInferenceModelsStatsBar'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.showAllLabel"
|
||||
defaultMessage="Show all"
|
||||
/>
|
||||
}
|
||||
checked={!!pageState.showAll}
|
||||
onChange={(e) => updatePageState({ showAll: e.target.checked })}
|
||||
data-test-subj="mlModelsShowAllSwitch"
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{modelsToDelete.length > 0 && (
|
||||
<DeleteModelsModal
|
||||
onClose={(refreshList) => {
|
||||
modelsToDelete.forEach((model) => {
|
||||
if (isBaseNLPModelItem(model) && model.state === MODEL_STATE.DOWNLOADING) {
|
||||
abortedDownload.current.add(model.model_id);
|
||||
}
|
||||
});
|
||||
|
||||
setItemIdToExpandedRowMap((prev) => {
|
||||
const newMap = { ...prev };
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType={'plusInCircle'}
|
||||
color={'primary'}
|
||||
onClick={setIsAddModelFlyoutVisible.bind(null, true)}
|
||||
data-test-subj="mlModelsAddTrainedModelButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.addModelButtonLabel"
|
||||
defaultMessage="Add trained model"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<div data-test-subj="mlModelsTableContainer">
|
||||
<EuiInMemoryTable<TrainedModelUIItem>
|
||||
tableLayout={'auto'}
|
||||
responsiveBreakpoint={'xl'}
|
||||
allowNeutralSort={false}
|
||||
columns={columns}
|
||||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
items={tableItems}
|
||||
itemId={ModelsTableToConfigMapping.id}
|
||||
loading={isLoading}
|
||||
search={search}
|
||||
selection={selection}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `mlModelsTableRow row-${item.model_id}`,
|
||||
})}
|
||||
pagination={pagination}
|
||||
onTableChange={onTableChange}
|
||||
sorting={sorting}
|
||||
data-test-subj={isLoading ? 'mlModelsTable loading' : 'mlModelsTable loaded'}
|
||||
childrenBetween={
|
||||
isElserCalloutVisible ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.newElserModelTitle"
|
||||
defaultMessage="New ELSER model now available"
|
||||
/>
|
||||
}
|
||||
onDismiss={setIsElserCalloutDismissed.bind(null, true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.newElserModelDescription"
|
||||
defaultMessage="A new version of ELSER that shows faster performance and improved relevance is now available. {docLink} for information on how to start using it."
|
||||
values={{
|
||||
docLink: (
|
||||
<EuiLink href={nlpElserDocUrl} external target={'_blank'}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.modelsList.startDeployment.viewElserDocLink"
|
||||
defaultMessage="View documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{modelsToDelete.length > 0 && (
|
||||
<DeleteModelsModal
|
||||
onClose={(refreshList) => {
|
||||
modelsToDelete.forEach((model) => {
|
||||
delete newMap[model.model_id];
|
||||
if (isBaseNLPModelItem(model) && model.state === MODEL_STATE.DOWNLOADING) {
|
||||
abortedDownload.current.add(model.model_id);
|
||||
}
|
||||
});
|
||||
return newMap;
|
||||
});
|
||||
|
||||
setModelsToDelete([]);
|
||||
setItemIdToExpandedRowMap((prev) => {
|
||||
const newMap = { ...prev };
|
||||
modelsToDelete.forEach((model) => {
|
||||
delete newMap[model.model_id];
|
||||
});
|
||||
return newMap;
|
||||
});
|
||||
|
||||
if (refreshList) {
|
||||
fetchModelsData();
|
||||
}
|
||||
}}
|
||||
models={modelsToDelete}
|
||||
/>
|
||||
)}
|
||||
{modelToTest === null ? null : (
|
||||
<TestModelAndPipelineCreationFlyout
|
||||
model={modelToTest}
|
||||
onClose={(refreshList?: boolean) => {
|
||||
setModelToTest(null);
|
||||
if (refreshList) {
|
||||
fetchModelsData();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{dfaModelToTest === null ? null : (
|
||||
<TestDfaModelsFlyout model={dfaModelToTest} onClose={setDfaModelToTest.bind(null, null)} />
|
||||
)}
|
||||
{modelToDeploy !== undefined ? (
|
||||
<AddInferencePipelineFlyout
|
||||
onClose={setModelToDeploy.bind(null, undefined)}
|
||||
model={modelToDeploy}
|
||||
/>
|
||||
) : null}
|
||||
{isAddModelFlyoutVisible ? (
|
||||
<AddModelFlyout
|
||||
modelDownloads={items.filter(isModelDownloadItem)}
|
||||
onClose={setIsAddModelFlyoutVisible.bind(null, false)}
|
||||
onSubmit={(modelId) => {
|
||||
onModelDownloadRequest(modelId);
|
||||
setIsAddModelFlyoutVisible(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
setModelsToDelete([]);
|
||||
|
||||
if (refreshList) {
|
||||
fetchModelsData();
|
||||
}
|
||||
}}
|
||||
models={modelsToDelete}
|
||||
/>
|
||||
)}
|
||||
{modelToTest === null ? null : (
|
||||
<TestModelAndPipelineCreationFlyout
|
||||
model={modelToTest}
|
||||
onClose={(refreshList?: boolean) => {
|
||||
setModelToTest(null);
|
||||
if (refreshList) {
|
||||
fetchModelsData();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{dfaModelToTest === null ? null : (
|
||||
<TestDfaModelsFlyout
|
||||
model={dfaModelToTest}
|
||||
onClose={setDfaModelToTest.bind(null, null)}
|
||||
/>
|
||||
)}
|
||||
{modelToDeploy !== undefined ? (
|
||||
<AddInferencePipelineFlyout
|
||||
onClose={setModelToDeploy.bind(null, undefined)}
|
||||
model={modelToDeploy}
|
||||
/>
|
||||
) : null}
|
||||
{isAddModelFlyoutVisible ? (
|
||||
<AddModelFlyout
|
||||
modelDownloads={items.filter(isModelDownloadItem)}
|
||||
onClose={setIsAddModelFlyoutVisible.bind(null, false)}
|
||||
onSubmit={(modelId) => {
|
||||
onModelDownloadRequest(modelId);
|
||||
setIsAddModelFlyoutVisible(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</SpaceManagementContextWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -52,7 +52,6 @@ const PageWrapper: FC = () => {
|
|||
...basicResolvers(),
|
||||
initSavedObjects,
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLoader context={context}>
|
||||
<MlPageHeader>
|
||||
|
@ -65,6 +64,7 @@ const PageWrapper: FC = () => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</MlPageHeader>
|
||||
|
||||
<ModelsList />
|
||||
</PageLoader>
|
||||
);
|
||||
|
|
|
@ -212,6 +212,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
|
|||
uiActions: pluginsStart.uiActions,
|
||||
unifiedSearch: pluginsStart.unifiedSearch,
|
||||
usageCollection: pluginsSetup.usageCollection,
|
||||
spaces: pluginsStart.spaces,
|
||||
},
|
||||
params,
|
||||
this.isServerless,
|
||||
|
|
|
@ -60,11 +60,19 @@ export const SpacesContextWrapperInternal = (
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
let unmounted = false;
|
||||
getStartServices().then(([coreStart]) => {
|
||||
if (unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { application, docLinks, notifications } = coreStart;
|
||||
const services = { application, docLinks, notifications };
|
||||
setContext(createSpacesReactContext(services, spacesManager, spacesDataPromise));
|
||||
});
|
||||
return () => {
|
||||
unmounted = true;
|
||||
};
|
||||
}, [getStartServices, spacesDataPromise, spacesManager]);
|
||||
|
||||
if (!context) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { USER } from '../../../services/ml/security_common';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
|
@ -115,7 +116,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testResources.createDataViewIfNeeded('ft_ihp_outlier', '@timestamp');
|
||||
|
||||
await ml.testResources.setKibanaTimeZoneToUTC();
|
||||
await ml.securityUI.loginAsMlPowerUser();
|
||||
await ml.securityUI.loginAs(USER.ML_POWERUSER_ALL_SPACES);
|
||||
|
||||
for (const spaceId of Object.values(spaceIds)) {
|
||||
if (spaceId !== 'default') {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue