[ML] Moves job and trained model management features into Stack Management (#204290)

## Summary

Updates the navigation for Machine Learning pages, moving admin tasks
for managing ML jobs and models to a single place inside Stack
Management, and leaving exploratory tasks in a consolidated top-level
Machine Learning menu.

The available items vary by solution, so that the navigation for an
Elasticsearch project, for example, contains a single item for managing
trained models.

#### Stack management menu for classic/observability/security nav
<img width="275" alt="Screenshot 2025-04-04 at 16 10 04"
src="https://github.com/user-attachments/assets/14b6e8d4-7111-4fbd-ae5d-9f389f83f23c"
/>

#### Stack management for search:
<img width="271" alt="Screenshot 2025-04-07 at 14 38 45"
src="https://github.com/user-attachments/assets/e104bf20-8a4d-4eed-9b5b-9c05944091ca"
/>


#### Machine Learning menu for Classic nav
<img width="341" alt="Screenshot 2025-04-07 at 14 22 03"
src="https://github.com/user-attachments/assets/610efd59-311f-410f-9881-548359ca7997"
/>


#### Machine Learning menu for Observability
<img width="522" alt="Screenshot 2025-04-04 at 16 11 48"
src="https://github.com/user-attachments/assets/ef16acf1-4d39-4494-a5d3-0fb078d74730"
/>


#### Machine Learning menu for Security
<img width="528" alt="Screenshot 2025-04-04 at 17 46 43"
src="https://github.com/user-attachments/assets/2df20c20-b894-4421-a732-9370bb5d6f2d"
/>


### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...

---------

Co-authored-by: Quynh Nguyen <quynh.nguyen@elastic.co>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Timothy Sullivan <tsullivan@elastic.co>
This commit is contained in:
Melissa Alvarez 2025-04-08 11:52:16 -06:00 committed by GitHub
parent b0c0917fa7
commit f51ac13197
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
291 changed files with 5816 additions and 4309 deletions

View file

@ -216,7 +216,7 @@ xpack.observabilityAIAssistant.scope: "observability"
telemetry.labels.serverless: observability
xpack.ml.ad.enabled: true
xpack.ml.dfa.enabled: false
xpack.ml.dfa.enabled: true
xpack.ml.nlp:
enabled: true
modelDeployment:

View file

@ -104,7 +104,7 @@ pageLoadAssetSize:
maps: 46000
mapsEms: 26072
metricsDataAccess: 73287
ml: 85000
ml: 89000
mockIdpPlugin: 30000
monitoring: 80000
navigation: 37269

View file

@ -32,12 +32,6 @@ export const defaultNavigation: MlNodeDefinition = {
{
link: 'ml:overview',
},
{
link: 'ml:notifications',
},
{
link: 'ml:memoryUsage',
},
{
title: i18n.translate('defaultNavigation.ml.anomalyDetection', {
defaultMessage: 'Anomaly Detection',

View file

@ -28,7 +28,10 @@ export type IntegrationsDeepLinkId = IntegrationsAppId | FleetAppId | OsQueryApp
// Management
export type ManagementAppId = typeof MANAGEMENT_APP_ID;
export type ManagementId =
| 'ad_settings'
| 'aiAssistantManagementSelection'
| 'analytics'
| 'anomaly_detection'
| 'securityAiAssistantManagement'
| 'observabilityAiAssistantManagement'
| 'api_keys'
@ -46,6 +49,7 @@ export type ManagementId =
| 'maintenanceWindows'
| 'migrate_data'
| 'objects'
| 'overview'
| 'pipelines'
| 'remote_clusters'
| 'reporting'
@ -56,7 +60,9 @@ export type ManagementId =
| 'settings'
| 'snapshot_restore'
| 'spaces'
| 'supplied_configurations'
| 'tags'
| 'trained_models'
| 'transform'
| 'triggersActions'
| 'triggersActionsConnectors'

View file

@ -34,6 +34,14 @@ const insightsAndAlertingTip = i18n.translate('management.sections.insightsAndAl
defaultMessage: 'Manage how to detect changes in your data',
});
const machineLearningTitle = i18n.translate('management.sections.machineLearningTitle', {
defaultMessage: 'Machine Learning',
});
const machineLearningTip = i18n.translate('management.sections.machineLearningTip', {
defaultMessage: 'Manage your Machine Learning jobs and trained models',
});
const sectionTitle = i18n.translate('management.sections.section.title', {
defaultMessage: 'Security',
});
@ -79,6 +87,13 @@ export const InsightsAndAlertingSection = {
order: 2,
};
export const MachineLearningSection = {
id: ManagementSectionId.MachineLearning,
title: machineLearningTitle,
tip: machineLearningTip,
order: 4,
};
export const SecuritySection = {
id: 'security',
title: sectionTitle,
@ -104,6 +119,7 @@ export const managementSections = [
IngestSection,
DataSection,
InsightsAndAlertingSection,
MachineLearningSection,
SecuritySection,
KibanaSection,
StackSection,

View file

@ -30,7 +30,7 @@ describe('ManagementService', () => {
managementService.start({ capabilities });
const start = getSectionsServiceStartPrivate();
expect(start.getSectionsEnabled().length).toEqual(6);
expect(start.getSectionsEnabled().length).toEqual(7);
});
test('Register section, enable and disable', () => {
@ -44,11 +44,11 @@ describe('ManagementService', () => {
managementService.start({ capabilities });
const start = getSectionsServiceStartPrivate();
expect(start.getSectionsEnabled().length).toEqual(7);
expect(start.getSectionsEnabled().length).toEqual(8);
testSection.disable();
expect(start.getSectionsEnabled().length).toEqual(6);
expect(start.getSectionsEnabled().length).toEqual(7);
});
test('Disables items that are not allowed by Capabilities', () => {

View file

@ -13,6 +13,7 @@ import {
IngestSection,
DataSection,
InsightsAndAlertingSection,
MachineLearningSection,
SecuritySection,
KibanaSection,
StackSection,
@ -41,6 +42,7 @@ export class ManagementSectionsService {
ingest: this.registerSection(IngestSection),
data: this.registerSection(DataSection),
insightsAndAlerting: this.registerSection(InsightsAndAlertingSection),
machineLearning: this.registerSection(MachineLearningSection),
security: this.registerSection(SecuritySection),
kibana: this.registerSection(KibanaSection),
stack: this.registerSection(StackSection),

View file

@ -31,6 +31,7 @@ export interface DefinedSections {
ingest: ManagementSection;
data: ManagementSection;
insightsAndAlerting: ManagementSection;
machineLearning: ManagementSection;
security: ManagementSection;
kibana: ManagementSection;
stack: ManagementSection;
@ -65,6 +66,7 @@ export enum ManagementSectionId {
Ingest = 'ingest',
Data = 'data',
InsightsAndAlerting = 'insightsAndAlerting',
MachineLearning = 'ml',
Security = 'security',
Kibana = 'kibana',
Stack = 'stack',

View file

@ -104,6 +104,10 @@ interface DatePickerWrapperProps {
* Tooltip message for the update button
*/
tooltipMessage?: string;
/**
* Data test subject for the refresh button
*/
dataTestSubj?: string;
}
/**
@ -123,6 +127,7 @@ export const DatePickerWrapper: FC<DatePickerWrapperProps> = (props) => {
needsUpdate,
onRefresh,
tooltipMessage,
dataTestSubj = 'mlDatePickerRefreshPageButton',
} = props;
const {
data,
@ -337,7 +342,7 @@ export const DatePickerWrapper: FC<DatePickerWrapperProps> = (props) => {
color={needsUpdate ? 'accentSecondary' : 'primary'}
iconType={needsUpdate ? 'kqlFunction' : 'refresh'}
onClick={handleRefresh}
data-test-subj={`mlDatePickerRefreshPageButton${isLoading ? ' loading' : ' loaded'}`}
data-test-subj={`${dataTestSubj}${isLoading ? ' loading' : ' loaded'}`}
isLoading={isLoading}
isDisabled={isDisabled}
>

View file

@ -128,7 +128,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
<EuiLink
data-test-subj="observabilityAiAssistantWelcomeMessageTrainedModelsLink"
external
href={http?.basePath.prepend('/app/ml/trained_models')}
href={http?.basePath.prepend('/app/management/ml/trained_models')}
target="_blank"
>
{i18n.translate('xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel', {

View file

@ -27163,7 +27163,6 @@
"xpack.ml.anomalyDetectionAlert.name": "Détection des anomalies",
"xpack.ml.anomalyDetectionAlert.topNBucketsDescription": "Nombre de groupes les plus récents à vérifier pour obtenir l'anomalie la plus élevée.",
"xpack.ml.anomalyDetectionAlert.topNBucketsLabel": "Nombre de groupes les plus récents",
"xpack.ml.anomalyDetectionBreadcrumbLabel": "Détection des anomalies",
"xpack.ml.anomalyExplorerPageLabel": "Anomaly Explorer (Explorateur d'anomalies)",
"xpack.ml.anomalyResultsViewSelector.anomalyExplorerLabel": "Voir les résultats dans Anomaly Explorer",
"xpack.ml.anomalyResultsViewSelector.buttonGroupLegend": "Sélecteur de vue des résultats d'anomalie",
@ -27301,7 +27300,6 @@
"xpack.ml.controls.selectSeverity.minorLabel": "mineure",
"xpack.ml.controls.selectSeverity.scoreDetailsDescription": "score {value} et supérieur",
"xpack.ml.controls.selectSeverity.warningLabel": "avertissement",
"xpack.ml.createJobsBreadcrumbLabel": "Créer une tâche",
"xpack.ml.creationWizardUtils.destinationIndexInputAriaLabel": "Choisissez un nom d'index de destination unique.",
"xpack.ml.creationWizardUtils.destinationIndexInvalidError": "Nom d'index de destination non valide.",
"xpack.ml.creationWizardUtils.destinationIndexInvalidErrorLink": "Découvrez les limitations relatives aux noms d'index.",
@ -27869,7 +27867,6 @@
"xpack.ml.dataFrameAnalyticsBreadcrumbs.dataViewLabel": "Vue de données",
"xpack.ml.dataFrameAnalyticsBreadcrumbs.esqlLabel": "Visualiseur de données pour les index (ES|QL)",
"xpack.ml.dataFrameAnalyticsBreadcrumbs.jobsManagementLabel": "Tâches",
"xpack.ml.dataFrameAnalyticsLabel": "Analyse du cadre de données",
"xpack.ml.dataGrid.CcsWarningCalloutBody": "Un problème est survenu lors de la récupération des données pour la vue de données. L'aperçu de la source combiné avec la recherche inter-clusters est uniquement pris en charge pour les versions 7.10 et supérieures. Toutefois, vous pouvez toujours configurer et créer la transformation.",
"xpack.ml.dataGrid.CcsWarningCalloutTitle": "La recherche inter-clusters n'a renvoyé aucune donnée de champ.",
"xpack.ml.dataGrid.columnChart.ErrorMessageToast": "Une erreur s'est produite lors de la récupération des données de l'histogramme : {error}",
@ -27904,8 +27901,6 @@
"xpack.ml.dataVisualizer.pageHeader": "Data Visualizer (Visualiseur de données)",
"xpack.ml.datavisualizer.selector.dataVisualizerDescription": "L'outil de Machine Learning Data Visualizer (Visualiseur de données) vous aide à comprendre vos données en analysant les indicateurs et les champs dans un fichier log ou un index Elasticsearch existant.",
"xpack.ml.datavisualizer.selector.dataVisualizerTitle": "Data Visualizer (Visualiseur de données)",
"xpack.ml.datavisualizer.selector.esqlTechnicalPreviewBadge.titleMsg": "Le visualiseur de données ES|QL est en version préliminaire technique.",
"xpack.ml.datavisualizer.selector.importDataDescription": "Importez les données à partir d'un fichier log. Vous pouvez charger des fichiers d'une taille allant jusqu'à {maxFileSize}.",
"xpack.ml.datavisualizer.selector.importDataTitle": "Visualiser les données à partir d'un fichier",
"xpack.ml.datavisualizer.selector.selectDataViewButtonLabel": "Sélectionner la vue de données",
"xpack.ml.datavisualizer.selector.selectDataViewTitle": "Visualiser les données à partir d'une vue de données",
@ -27937,27 +27932,21 @@
"xpack.ml.deepLink.analyticsMap": "Mapping d'analyse",
"xpack.ml.deepLink.anomalyDetection": "Détection des anomalies",
"xpack.ml.deepLink.anomalyExplorer": "Explorateur d'anomalies",
"xpack.ml.deepLink.calendarSettings": "Calendriers",
"xpack.ml.deepLink.changePointDetection": "Modifier la détection du point",
"xpack.ml.deepLink.dataDrift": "Dérive de données",
"xpack.ml.deepLink.dataFrameAnalytics": "Analyse du cadre de données",
"xpack.ml.deepLink.dataVisualizer": "Data Visualizer (Visualiseur de données)",
"xpack.ml.deepLink.esqlDataVisualizer": "Visualiseur de données ES|QL",
"xpack.ml.deepLink.fileUpload": "Chargement du fichier",
"xpack.ml.deepLink.filterListsSettings": "Listes de filtres",
"xpack.ml.deepLink.indexDataVisualizer": "Index Data Visualizer (Visualiseur de données pour les index)",
"xpack.ml.deepLink.logPatternAnalysis": "Analyse du modèle de log",
"xpack.ml.deepLink.logRateAnalysis": "Analyse du Taux de Log",
"xpack.ml.deepLink.memoryUsage": "Utilisation mémoire",
"xpack.ml.deepLink.modelManagement": "Gestion des modèles",
"xpack.ml.deepLink.nodes": "Nœuds",
"xpack.ml.deepLink.notifications": "Notifications",
"xpack.ml.deepLink.overview": "Aperçu",
"xpack.ml.deepLink.resultExplorer": "Explorateur de résultats",
"xpack.ml.deepLink.settings": "Paramètres",
"xpack.ml.deepLink.singleMetricViewer": "Visionneuse dindicateur unique",
"xpack.ml.deepLink.suppliedConfigurations": "Configurations fournies",
"xpack.ml.deepLink.trainedModels": "Modèles entraînés",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanDelete.job": "Continuer pour supprimer {length, plural, one {# tâche} other {# tâches}}",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanDelete.model": "Continuer pour supprimer {length, plural, one {# modèle} other {# modèles}}",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanUnTagConfirm": "Retirer de l'espace en cours",
@ -28179,11 +28168,6 @@
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingCalendarsButton": "Tâches utilisant des calendriers",
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingFiltersAria": "Tâches utilisant des listes de filtres",
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingFiltersButton": "Tâches utilisant des listes de filtres",
"xpack.ml.importExport.exportFlyout.flyoutHeader": "Exporter les tâches",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.cancelButton": "Annuler",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.confirmButton": "Confirmer",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.text": "Le changement d'onglets effacera la sélection actuelle des tâches",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.title": "Changer d'onglets ?",
"xpack.ml.importExport.importButton": "Importer les tâches",
"xpack.ml.importExport.importFlyout.cannotImportJobCallout.jobListAria": "afficher les tâches",
"xpack.ml.importExport.importFlyout.cannotImportJobCallout.jobListButton": "Afficher les tâches",
@ -28594,7 +28578,6 @@
"xpack.ml.management.jobsList.noPermissionToAccessLabel": "Accès refusé",
"xpack.ml.management.jobsList.syncFlyoutButton": "Synchroniser les objets enregistrés",
"xpack.ml.management.jobsList.trainedModelsDocsLabel": "Documents relatifs aux modèles entraînés",
"xpack.ml.management.jobsListTitle": "Machine Learning",
"xpack.ml.management.jobsSpacesList.jobObjectNoun": "tâche",
"xpack.ml.management.jobsSpacesList.modelObjectNoun": "modèle entraîné",
"xpack.ml.management.jobsSpacesList.updateSpaces.error": "Erreur lors de la mise à jour de {id}",
@ -28666,9 +28649,6 @@
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.createJobButtonText": "Créer une tâche",
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.createJobMessage": "Créer une tâche de détection des anomalies",
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.emptyPromptText": "La détection des anomalies permet d'identifier un comportement inhabituel dans des données géographiques. Créez une tâche utilisant la fonction lat_long, requise pour la couche d'anomalies de mapping.",
"xpack.ml.memoryUsage.memoryTab": "Utilisation mémoire",
"xpack.ml.memoryUsage.memoryUsageHeader": "Utilisation mémoire",
"xpack.ml.memoryUsage.nodesTab": "Nœuds",
"xpack.ml.memoryUsage.treeMap.adLabel": "Tâches de détection des anomalies",
"xpack.ml.memoryUsage.treeMap.dfaLabel": "Tâches d'analyse du cadre de données",
"xpack.ml.memoryUsage.treeMap.emptyPrompt": "Aucune tâche ouverte ni aucun modèle entraîné ne correspond à la sélection actuelle.",
@ -28679,10 +28659,8 @@
"xpack.ml.mlEntitySelector.dfaOptionsLabel": "Analyse du cadre de données",
"xpack.ml.mlEntitySelector.fetchError": "Impossible de récupérer les entités de ML",
"xpack.ml.mlEntitySelector.trainedModelsLabel": "Modèles entraînés",
"xpack.ml.modelManagement.memoryUsage.docTitle": "Utilisation mémoire",
"xpack.ml.modelManagement.trainedModels.docTitle": "Modèles entraînés",
"xpack.ml.modelManagement.trainedModelsHeader": "Modèles entraînés",
"xpack.ml.modelManagementLabel": "Gestion des modèles",
"xpack.ml.models.dfaValidation.messages.analysisFieldsEmptyWarningText": "Certains champs inclus pour l'analyse ont au moins {percentEmpty} % de valeurs vides et peuvent ne pas être adaptés à l'analyse.",
"xpack.ml.models.dfaValidation.messages.analysisFieldsHeading": "Champs d'analyse",
"xpack.ml.models.dfaValidation.messages.analysisFieldsHighWarningText": "Plus de {includedFieldsThreshold} champs sont sélectionnés pour l'analyse. Cela peut augmenter l'utilisation des ressources et allonger le temps d'exécution des tâches.",
@ -28851,31 +28829,21 @@
"xpack.ml.multiSelectPicker.NoFiltersFoundMessage": "Aucun filtre trouvé",
"xpack.ml.navMenu.aiopsTabLinkText": "AIOps Labs",
"xpack.ml.navMenu.anomalyDetection.anomalyExplorerText": "Anomaly Explorer (Explorateur d'anomalies)",
"xpack.ml.navMenu.anomalyDetection.jobsManagementText": "Tâches",
"xpack.ml.navMenu.anomalyDetection.singleMetricViewerText": "Single Metric Viewer (Visionneuse d'indicateur unique)",
"xpack.ml.navMenu.anomalyDetection.suppliedConfigurationsLinkText": "Configurations fournies",
"xpack.ml.navMenu.anomalyDetectionTabLinkText": "Détection des anomalies",
"xpack.ml.navMenu.changePointDetectionLinkText": "Modifier la détection du point",
"xpack.ml.navMenu.dataComparisonText": "Dérive de données",
"xpack.ml.navMenu.dataFrameAnalytics.analyticsMapText": "Mapping d'analyse",
"xpack.ml.navMenu.dataFrameAnalytics.jobsManagementText": "Tâches",
"xpack.ml.navMenu.dataFrameAnalytics.resultsExplorerText": "Explorateur de résultats",
"xpack.ml.navMenu.dataFrameAnalyticsTabLinkText": "Analyse du cadre de données",
"xpack.ml.navMenu.dataViewDataVisualizerLinkText": "Vue de données",
"xpack.ml.navMenu.dataVisualizerTabLinkText": "Data Visualizer (Visualiseur de données)",
"xpack.ml.navMenu.esqlDataVisualizerLinkText": "ES|QL",
"xpack.ml.navMenu.fileDataVisualizerLinkText": "Fichier",
"xpack.ml.navMenu.logCategorizationLinkText": "Analyse du modèle de log",
"xpack.ml.navMenu.logRateAnalysisLinkText": "Analyse du Taux de Log",
"xpack.ml.navMenu.memoryUsageText": "Utilisation mémoire",
"xpack.ml.navMenu.mlAppNameText": "Machine Learning et Analytique",
"xpack.ml.navMenu.modelManagementText": "Gestion des modèles",
"xpack.ml.navMenu.notificationsTabLinkText": "Notifications",
"xpack.ml.navMenu.overviewTabLinkText": "Aperçu",
"xpack.ml.navMenu.settingsTabLinkText": "Paramètres",
"xpack.ml.navMenu.trainedModelsTabBetaLabel": "Version dévaluation technique",
"xpack.ml.navMenu.trainedModelsTabBetaTooltipContent": "Cette fonctionnalité est en version d'évaluation technique et pourra être modifiée ou retirée complètement dans une future version. Elastic s'efforcera de corriger tout problème, mais les fonctionnalités des versions d'évaluation technique ne sont pas soumises aux SLA de support des fonctionnalités officielles en disponibilité générale.",
"xpack.ml.navMenu.trainedModelsText": "Modèles entraînés",
"xpack.ml.newJob.fromGeo.createJob.error.noTimeRange": "Plage temporelle non spécifiée.",
"xpack.ml.newJob.fromLens.createJob.error.colsNoSourceField": "Certaines colonnes ne contiennent pas de champ source.",
"xpack.ml.newJob.fromLens.createJob.error.colsUsingFilterTimeSift": "Les colonnes contenant des paramètres incompatibles avec les détecteurs de ML, le décalage temporel et la fonction Filtrer par ne sont pas prises en charge.",
@ -29380,7 +29348,6 @@
"xpack.ml.overview.nodesPanel.header": "Nœuds",
"xpack.ml.overview.nodesPanel.totalNodesLabel": "Total",
"xpack.ml.overview.nodesPanel.viewNodeLink": "Afficher les nœuds",
"xpack.ml.overview.notificationsLabel": "Notifications",
"xpack.ml.overview.overviewLabel": "Aperçu",
"xpack.ml.overview.statsBar.failedAnalyticsLabel": "Échoué",
"xpack.ml.overview.statsBar.runningAnalyticsLabel": "En cours d'exécution",
@ -29509,7 +29476,6 @@
"xpack.ml.sampleDataLinkLabel": "Tâches de ML",
"xpack.ml.savedObjectFinder.createADataView": "Créer une vue de données",
"xpack.ml.selectDataViewLabel": "Sélectionner la vue de données",
"xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "Détection des anomalies",
"xpack.ml.settings.anomalyDetection.calendarsDstSummaryCount": "Vous avez {calendarsCountBadge} {calendarsDstCount, plural, one {calendrier} other {calendriers}}",
"xpack.ml.settings.anomalyDetection.calendarsDstText": "Les calendriers DST contiennent une liste d'événements programmés pour lesquels vous ne souhaitez pas générer d'anomalies, en tenant compte des décalages de l'heure d'été qui peuvent entraîner l'apparition d'événements une heure plus tôt ou plus tard.",
"xpack.ml.settings.anomalyDetection.calendarsDstTitle": "Calendriers DST",
@ -29529,11 +29495,9 @@
"xpack.ml.settings.anomalyDetection.manageFilterListsLink": "Gérer",
"xpack.ml.settings.breadcrumbs.calendarManagement.createLabel": "Créer",
"xpack.ml.settings.breadcrumbs.calendarManagement.editLabel": "Modifier",
"xpack.ml.settings.breadcrumbs.calendarManagementLabel": "Gestion du calendrier DST",
"xpack.ml.settings.breadcrumbs.dataComparisonLabel": "Dérive de données",
"xpack.ml.settings.breadcrumbs.filterLists.createLabel": "Créer",
"xpack.ml.settings.breadcrumbs.filterLists.editLabel": "Modifier",
"xpack.ml.settings.breadcrumbs.filterListsLabel": "Liste de filtres",
"xpack.ml.settings.calendarList.docTitle": "Calendriers",
"xpack.ml.settings.calendars.listHeader.calendarsDescription": "Les calendriers contiennent une liste d'événements programmés pour lesquels vous ne souhaitez pas générer d'anomalies, tels que des pannes système planifiées ou les jours fériés. Un même calendrier peut être affecté à plusieurs tâches.{br}{learnMoreLink}",
"xpack.ml.settings.calendars.listHeader.calendarsDescription.learnMoreLinkText": "En savoir plus",
@ -29593,7 +29557,6 @@
"xpack.ml.settings.filterLists.table.noFiltersCreatedTitle": "Aucun filtre n'a été créé",
"xpack.ml.settings.filterLists.table.notInUseAriaLabel": "Pas en cours d'utilisation",
"xpack.ml.settings.filterLists.toolbar.deleteItemButtonLabel": "Supprimer un élément",
"xpack.ml.settings.title": "Paramètres",
"xpack.ml.settingsBreadcrumbLabel": "Paramètres",
"xpack.ml.severitySelector.formControlAriaLabel": "Sélectionner le seuil de sévérité",
"xpack.ml.severitySelector.formControlLabel": "Sévérité",
@ -29628,8 +29591,6 @@
"xpack.ml.suppliedConfigurations.preconfigurecJobsHeader": "Configurations fournies",
"xpack.ml.suppliedConfigurations.preconfigurecJobsHeaderDescription": "Cette page répertorie les configurations de tâches de détection d'anomalies prédéfinies avec les ressources Kibana associées.",
"xpack.ml.suppliedConfigurations.suppliedConfigurations.docTitle": "Configurations fournies",
"xpack.ml.suppliedConfigurationsBreadcrumbs.suppliedConfigurationsLabel": "Configurations fournies",
"xpack.ml.suppliedConfigurationsLabel": "Configurations fournies",
"xpack.ml.swimlaneEmbeddable.errorMessage": "Impossible de charger les données du couloir de ML",
"xpack.ml.swimlaneEmbeddable.noDataFound": "Aucune anomalie n'a été trouvée",
"xpack.ml.swimlaneEmbeddable.panelTitleLabel": "Titre du panneau",
@ -30131,7 +30092,6 @@
"xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.inputText": "Entrer une expression à tester",
"xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.label": "Classification Zero-Shot",
"xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel": "Dérive de données",
"xpack.ml.trainedModelsBreadcrumbs.nodeOverviewLabel": "Utilisation mémoire",
"xpack.ml.trainedModelsBreadcrumbs.trainedModelsLabel": "Modèles entraînés",
"xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescription": "Les index associés au Machine Learning sont actuellement en cours de mise à niveau.",
"xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescriptionExtra": "Certaines actions ne seront pas disponibles pendant cette opération.",
@ -32425,23 +32385,13 @@
"xpack.observability.obltNav.infrastructure.metricsExplorer": "Explorateur d'indicateurs",
"xpack.observability.obltNav.infrastructure.universalProfiling": "Profilage universel",
"xpack.observability.obltNav.machineLearning": "Machine Learning",
"xpack.observability.obltNav.machineLearning.memoryUsage": "Utilisation mémoire",
"xpack.observability.obltNav.management": "Gestion",
"xpack.observability.obltNav.ml.aiops_labs": "Ateliers AIOps",
"xpack.observability.obltNav.ml.aiops_labs.change_point_detection": "Modifier la détection du point",
"xpack.observability.obltNav.ml.aiops_labs.log_pattern_analysis": "Analyse du modèle de log",
"xpack.observability.obltNav.ml.aiops_labs.log_rate_analysis": "Analyse du taux de log",
"xpack.observability.obltNav.ml.anomaly_detection": "Détection des anomalies",
"xpack.observability.obltNav.ml.anomaly_detection.jobs": "Tâches",
"xpack.observability.obltNav.ml.data_frame_analytics": "Analyse du cadre de données",
"xpack.observability.obltNav.ml.data_frame_analytics.jobs": "Tâches",
"xpack.observability.obltNav.ml.data_visualizer": "Data Visualizer (Visualiseur de données)",
"xpack.observability.obltNav.ml.data_visualizer.data_drift": "Dérive de données",
"xpack.observability.obltNav.ml.data_visualizer.data_view_data_visualizer": "Data Visualizer (Visualiseur de données)",
"xpack.observability.obltNav.ml.data_visualizer.esql_data_visualizer": "Visualiseur de données ES|QL",
"xpack.observability.obltNav.ml.data_visualizer.file_data_visualizer": "File Data Visualizer (Visualiseur de données pour les fichiers)",
"xpack.observability.obltNav.ml.model_management": "Gestion des modèles",
"xpack.observability.obltNav.ml.model_management.trainedModels": "Modèles entraînés",
"xpack.observability.obltNav.otherTools": "Autres outils",
"xpack.observability.obltNav.otherTools.logsAnomalies": "Anomalies des logs",
"xpack.observability.obltNav.otherTools.logsCategories": "Bibliothèque Visualize",
@ -32860,20 +32810,6 @@
"xpack.observabilityShared.bottomBarActions.unsavedChanges": "{unsavedChangesCount, plural, =0{0 modification non enregistrée} one {1 modification non enregistrée} other {# modifications non enregistrées}}",
"xpack.observabilityShared.breadcrumbs.observabilityLinkText": "Observabilité",
"xpack.observabilityShared.common.constants.grouping": "Observabilité",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemDescription": "Guides détaillés des fonctionnalités d'Elastic",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLabel": "Parcourir la documentation",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLinkARIALabel": "En savoir plus sur toutes les fonctionnalités d'Elastic",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLinkLabel": "En savoir plus",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemDescription": "Explorer notre environnement de démonstration en direct",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemLabel": "Environnement de démonstration",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemLinkLabel": "Explorer la démonstration",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemDescription": "Échanger à propos d'Elastic",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLabel": "Explorer le forum",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLinkARIALabel": "Ouvrir le forum de discussion sur Elastic",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLinkLabel": "Forum de discussion",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemDescription": "Obtenez de l'aide dans louverture dun cas",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemLabel": "Hub de support technique",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemLinkLabel": "Ouvrir le Hub de support technique",
"xpack.observabilityShared.featureFeedbackButton.tellUsWhatYouThinkLink": "Dites-nous ce que vous pensez !",
"xpack.observabilityShared.fieldValueSelection.apply": "Appliquer",
"xpack.observabilityShared.fieldValueSelection.apply.label": "Appliquer les filtres sélectionnés pour {label}",
@ -41874,21 +41810,12 @@
"xpack.serverlessObservability.nav.infrastructure": "Infrastructure",
"xpack.serverlessObservability.nav.infrastructureInventory": "Inventaire de l'infrastructure",
"xpack.serverlessObservability.nav.machineLearning": "Machine Learning",
"xpack.serverlessObservability.nav.machineLearning.memoryUsage": "Utilisation mémoire",
"xpack.serverlessObservability.nav.ml.aiops_labs": "Ateliers AIOps",
"xpack.serverlessObservability.nav.ml.aiops_labs.change_point_detection": "Modifier la détection du point",
"xpack.serverlessObservability.nav.ml.aiops_labs.log_pattern_analysis": "Analyse du modèle de log",
"xpack.serverlessObservability.nav.ml.aiops_labs.log_rate_analysis": "Analyse du taux de log",
"xpack.serverlessObservability.nav.ml.anomaly_detection": "Détection des anomalies",
"xpack.serverlessObservability.nav.ml.anomaly_detection.jobs": "Tâches",
"xpack.serverlessObservability.nav.ml.data_frame_analytics": "Analyse du cadre de données",
"xpack.serverlessObservability.nav.ml.data_frame_analytics.jobs": "Tâches",
"xpack.serverlessObservability.nav.ml.data_visualizer": "Data Visualizer (Visualiseur de données)",
"xpack.serverlessObservability.nav.ml.data_visualizer.data_drift": "Dérive de données",
"xpack.serverlessObservability.nav.ml.data_visualizer.data_view_data_visualizer": "Data Visualizer (Visualiseur de données)",
"xpack.serverlessObservability.nav.ml.data_visualizer.file_data_visualizer": "File Data Visualizer (Visualiseur de données pour les fichiers)",
"xpack.serverlessObservability.nav.ml.model_management": "Gestion des modèles",
"xpack.serverlessObservability.nav.ml.model_management.trainedModels": "Modèles entraînés",
"xpack.serverlessObservability.nav.mngt": "Gestion",
"xpack.serverlessObservability.nav.mngt.access": "Accès",
"xpack.serverlessObservability.nav.mngt.alertsAndInsights": "Alertes et informations exploitables",

View file

@ -27144,7 +27144,6 @@
"xpack.ml.anomalyDetectionAlert.name": "異常検知",
"xpack.ml.anomalyDetectionAlert.topNBucketsDescription": "最高の異常を取得するために確認する最新のバケット数。",
"xpack.ml.anomalyDetectionAlert.topNBucketsLabel": "最新のバケット数",
"xpack.ml.anomalyDetectionBreadcrumbLabel": "異常検知",
"xpack.ml.anomalyExplorerPageLabel": "異常エクスプローラー",
"xpack.ml.anomalyResultsViewSelector.anomalyExplorerLabel": "異常エクスプローラーで結果を表示",
"xpack.ml.anomalyResultsViewSelector.buttonGroupLegend": "異常結果ビューセレクター",
@ -27281,7 +27280,6 @@
"xpack.ml.controls.selectSeverity.minorLabel": "マイナー",
"xpack.ml.controls.selectSeverity.scoreDetailsDescription": "スコア{value}以上",
"xpack.ml.controls.selectSeverity.warningLabel": "警告",
"xpack.ml.createJobsBreadcrumbLabel": "ジョブを作成",
"xpack.ml.creationWizardUtils.destinationIndexInputAriaLabel": "固有の宛先インデックス名を選択してください。",
"xpack.ml.creationWizardUtils.destinationIndexInvalidError": "無効なデスティネーションインデックス名。",
"xpack.ml.creationWizardUtils.destinationIndexInvalidErrorLink": "インデックス名の制限に関する詳細。",
@ -27847,7 +27845,6 @@
"xpack.ml.dataFrameAnalyticsBreadcrumbs.dataViewLabel": "データビュー",
"xpack.ml.dataFrameAnalyticsBreadcrumbs.esqlLabel": "インデックスデータビジュアライザーES|QL",
"xpack.ml.dataFrameAnalyticsBreadcrumbs.jobsManagementLabel": "ジョブ",
"xpack.ml.dataFrameAnalyticsLabel": "データフレーム分析",
"xpack.ml.dataGrid.CcsWarningCalloutBody": "データビューのデータの取得中に問題が発生しました。ソースプレビューとクラスター横断検索を組み合わせることは、バージョン7.10以上ではサポートされていません。トランスフォームを構成して作成することはできます。",
"xpack.ml.dataGrid.CcsWarningCalloutTitle": "クラスター横断検索でフィールドデータが返されませんでした。",
"xpack.ml.dataGrid.columnChart.ErrorMessageToast": "ヒストグラムデータの取得でエラーが発生しました。{error}",
@ -27883,7 +27880,6 @@
"xpack.ml.datavisualizer.selector.dataVisualizerDescription": "機械学習データビジュアライザーツールは、ログファイルのメトリックとフィールド、または既存の Elasticsearch インデックスを分析し、データの理解を助けます。",
"xpack.ml.datavisualizer.selector.dataVisualizerTitle": "データビジュアライザー",
"xpack.ml.datavisualizer.selector.esqlTechnicalPreviewBadge.titleMsg": "ES|QLデータビジュアライザーはテクニカルプレビュー段階です。",
"xpack.ml.datavisualizer.selector.importDataDescription": "ログファイルからデータをインポートします。最大{maxFileSize}のファイルをアップロードできます。",
"xpack.ml.datavisualizer.selector.importDataTitle": "ファイルのデータを可視化",
"xpack.ml.datavisualizer.selector.selectDataViewButtonLabel": "データビューを選択",
"xpack.ml.datavisualizer.selector.selectDataViewTitle": "データビューのデータを可視化",
@ -27915,27 +27911,21 @@
"xpack.ml.deepLink.analyticsMap": "分析マップ",
"xpack.ml.deepLink.anomalyDetection": "異常検知",
"xpack.ml.deepLink.anomalyExplorer": "異常エクスプローラー",
"xpack.ml.deepLink.calendarSettings": "カレンダー",
"xpack.ml.deepLink.changePointDetection": "変化点検出",
"xpack.ml.deepLink.dataDrift": "データドリフト",
"xpack.ml.deepLink.dataFrameAnalytics": "データフレーム分析",
"xpack.ml.deepLink.dataVisualizer": "データビジュアライザー",
"xpack.ml.deepLink.esqlDataVisualizer": "ES|QLデータビジュアライザー",
"xpack.ml.deepLink.fileUpload": "ファイルアップロード",
"xpack.ml.deepLink.filterListsSettings": "フィルターリスト",
"xpack.ml.deepLink.indexDataVisualizer": "インデックスデータビジュアライザー",
"xpack.ml.deepLink.logPatternAnalysis": "ログパターン分析",
"xpack.ml.deepLink.logRateAnalysis": "ログレート分析",
"xpack.ml.deepLink.memoryUsage": "メモリー使用状況",
"xpack.ml.deepLink.modelManagement": "モデル管理",
"xpack.ml.deepLink.nodes": "ノード",
"xpack.ml.deepLink.notifications": "通知",
"xpack.ml.deepLink.overview": "概要",
"xpack.ml.deepLink.resultExplorer": "結果エクスプローラー",
"xpack.ml.deepLink.settings": "設定",
"xpack.ml.deepLink.singleMetricViewer": "シングルメトリックビューアー",
"xpack.ml.deepLink.suppliedConfigurations": "提供された構成",
"xpack.ml.deepLink.trainedModels": "学習済みモデル",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanDelete.job": "続行して、{length, plural, other {# 個のジョブ}}を削除します",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanDelete.model": "続行して、{length, plural, other {# 個のモデル}}を削除します",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanUnTagConfirm": "現在のスペースから削除",
@ -28158,11 +28148,6 @@
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingCalendarsButton": "カレンダーを使用したジョブ",
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingFiltersAria": "フィルターリストを使用したジョブ",
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingFiltersButton": "フィルターリストを使用したジョブ",
"xpack.ml.importExport.exportFlyout.flyoutHeader": "ジョブのエクスポート",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.cancelButton": "キャンセル",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.confirmButton": "確認",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.text": "タブを変更すると、現在選択しているジョブがクリアされます",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.title": "タブを変更しますか?",
"xpack.ml.importExport.importButton": "ジョブのインポート",
"xpack.ml.importExport.importFlyout.cannotImportJobCallout.jobListAria": "ジョブを表示",
"xpack.ml.importExport.importFlyout.cannotImportJobCallout.jobListButton": "ジョブを表示",
@ -28575,7 +28560,6 @@
"xpack.ml.management.jobsList.noPermissionToAccessLabel": "アクセスが拒否されました",
"xpack.ml.management.jobsList.syncFlyoutButton": "保存されたオブジェクトを同期",
"xpack.ml.management.jobsList.trainedModelsDocsLabel": "学習済みモデルドキュメント",
"xpack.ml.management.jobsListTitle": "機械学習",
"xpack.ml.management.jobsSpacesList.jobObjectNoun": "ジョブ",
"xpack.ml.management.jobsSpacesList.modelObjectNoun": "学習済みモデル",
"xpack.ml.management.jobsSpacesList.updateSpaces.error": "{id} の更新エラー",
@ -28647,9 +28631,6 @@
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.createJobButtonText": "ジョブを作成",
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.createJobMessage": "異常検知ジョブを作成しますか?",
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.emptyPromptText": "異常検知により、地理データの異常な動作を検出できます。lat_long関数を使用するジョブを作成します。これはMaps異常レイヤーで必要です。",
"xpack.ml.memoryUsage.memoryTab": "メモリー使用状況",
"xpack.ml.memoryUsage.memoryUsageHeader": "メモリー使用状況",
"xpack.ml.memoryUsage.nodesTab": "ノード",
"xpack.ml.memoryUsage.treeMap.adLabel": "異常検知ジョブ",
"xpack.ml.memoryUsage.treeMap.dfaLabel": "データフレーム分析ジョブ",
"xpack.ml.memoryUsage.treeMap.emptyPrompt": "現在の選択と一致する開いているジョブまたは学習済みモデルがありません。",
@ -28660,10 +28641,8 @@
"xpack.ml.mlEntitySelector.dfaOptionsLabel": "データフレーム分析",
"xpack.ml.mlEntitySelector.fetchError": "MLエンティティを取得できませんでした",
"xpack.ml.mlEntitySelector.trainedModelsLabel": "学習済みモデル",
"xpack.ml.modelManagement.memoryUsage.docTitle": "メモリー使用状況",
"xpack.ml.modelManagement.trainedModels.docTitle": "学習済みモデル",
"xpack.ml.modelManagement.trainedModelsHeader": "学習済みモデル",
"xpack.ml.modelManagementLabel": "モデル管理",
"xpack.ml.models.dfaValidation.messages.analysisFieldsEmptyWarningText": "分析に含まれる一部のフィールドには{percentEmpty}%以上の空の値があり、分析には適していない可能性があります。",
"xpack.ml.models.dfaValidation.messages.analysisFieldsHeading": "分析フィールド",
"xpack.ml.models.dfaValidation.messages.analysisFieldsHighWarningText": "{includedFieldsThreshold}を超えるフィールドが分析に選択されています。リソース使用量が増加し、ジョブの実行に時間がかかる場合があります。",
@ -28832,31 +28811,21 @@
"xpack.ml.multiSelectPicker.NoFiltersFoundMessage": "フィルターが見つかりません",
"xpack.ml.navMenu.aiopsTabLinkText": "AIOps Labs",
"xpack.ml.navMenu.anomalyDetection.anomalyExplorerText": "異常エクスプローラー",
"xpack.ml.navMenu.anomalyDetection.jobsManagementText": "ジョブ",
"xpack.ml.navMenu.anomalyDetection.singleMetricViewerText": "シングルメトリックビューアー",
"xpack.ml.navMenu.anomalyDetection.suppliedConfigurationsLinkText": "提供された構成",
"xpack.ml.navMenu.anomalyDetectionTabLinkText": "異常検知",
"xpack.ml.navMenu.changePointDetectionLinkText": "変化点検出",
"xpack.ml.navMenu.dataComparisonText": "データドリフト",
"xpack.ml.navMenu.dataFrameAnalytics.analyticsMapText": "分析マップ",
"xpack.ml.navMenu.dataFrameAnalytics.jobsManagementText": "ジョブ",
"xpack.ml.navMenu.dataFrameAnalytics.resultsExplorerText": "結果エクスプローラー",
"xpack.ml.navMenu.dataFrameAnalyticsTabLinkText": "データフレーム分析",
"xpack.ml.navMenu.dataViewDataVisualizerLinkText": "データビュー",
"xpack.ml.navMenu.dataVisualizerTabLinkText": "データビジュアライザー",
"xpack.ml.navMenu.esqlDataVisualizerLinkText": "ES|QL",
"xpack.ml.navMenu.fileDataVisualizerLinkText": "ファイル",
"xpack.ml.navMenu.logCategorizationLinkText": "ログパターン分析",
"xpack.ml.navMenu.logRateAnalysisLinkText": "ログレート分析",
"xpack.ml.navMenu.memoryUsageText": "メモリー使用状況",
"xpack.ml.navMenu.mlAppNameText": "機械学習と分析",
"xpack.ml.navMenu.modelManagementText": "モデル管理",
"xpack.ml.navMenu.notificationsTabLinkText": "通知",
"xpack.ml.navMenu.overviewTabLinkText": "概要",
"xpack.ml.navMenu.settingsTabLinkText": "設定",
"xpack.ml.navMenu.trainedModelsTabBetaLabel": "テクニカルプレビュー",
"xpack.ml.navMenu.trainedModelsTabBetaTooltipContent": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticはすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。",
"xpack.ml.navMenu.trainedModelsText": "学習済みモデル",
"xpack.ml.newJob.fromGeo.createJob.error.noTimeRange": "時間範囲が指定されていません。",
"xpack.ml.newJob.fromLens.createJob.error.colsNoSourceField": "一部の列にはソースフィールドがありません。",
"xpack.ml.newJob.fromLens.createJob.error.colsUsingFilterTimeSift": "ML検知器に対応していない設定が列に含まれています。時間シフトとフィルター条件はサポートされていません。",
@ -29361,7 +29330,6 @@
"xpack.ml.overview.nodesPanel.header": "ノード",
"xpack.ml.overview.nodesPanel.totalNodesLabel": "合計",
"xpack.ml.overview.nodesPanel.viewNodeLink": "ノードの表示",
"xpack.ml.overview.notificationsLabel": "通知",
"xpack.ml.overview.overviewLabel": "概要",
"xpack.ml.overview.statsBar.failedAnalyticsLabel": "失敗",
"xpack.ml.overview.statsBar.runningAnalyticsLabel": "実行中",
@ -29490,7 +29458,6 @@
"xpack.ml.sampleDataLinkLabel": "ML ジョブ",
"xpack.ml.savedObjectFinder.createADataView": "データビューを作成",
"xpack.ml.selectDataViewLabel": "データビューを選択",
"xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "異常検知",
"xpack.ml.settings.anomalyDetection.calendarsDstSummaryCount": "{calendarsCountBadge} {calendarsDstCount, plural, other {個のカレンダー}}があります",
"xpack.ml.settings.anomalyDetection.calendarsDstText": "DSTカレンダーには、異常を生成すべきではないスケジュールされたイベントのリストが含まれています。夏時間の時差によりイベントが1時間早くなったり遅くなったりする可能性が考慮されています。",
"xpack.ml.settings.anomalyDetection.calendarsDstTitle": "DSTカレンダー",
@ -29510,11 +29477,9 @@
"xpack.ml.settings.anomalyDetection.manageFilterListsLink": "管理",
"xpack.ml.settings.breadcrumbs.calendarManagement.createLabel": "作成",
"xpack.ml.settings.breadcrumbs.calendarManagement.editLabel": "編集",
"xpack.ml.settings.breadcrumbs.calendarManagementLabel": "カレンダーDST管理",
"xpack.ml.settings.breadcrumbs.dataComparisonLabel": "データドリフト",
"xpack.ml.settings.breadcrumbs.filterLists.createLabel": "作成",
"xpack.ml.settings.breadcrumbs.filterLists.editLabel": "編集",
"xpack.ml.settings.breadcrumbs.filterListsLabel": "フィルターリスト",
"xpack.ml.settings.calendarList.docTitle": "カレンダー",
"xpack.ml.settings.calendars.listHeader.calendarsDescription": "システム停止日や祝日など、異常値を生成したくないイベントについては、カレンダーに予定されているイベントのリストを登録できます。カレンダーは複数のジョブに割り当てることができます。{br}{learnMoreLink}",
"xpack.ml.settings.calendars.listHeader.calendarsDescription.learnMoreLinkText": "詳細",
@ -29573,7 +29538,6 @@
"xpack.ml.settings.filterLists.table.noFiltersCreatedTitle": "フィルターが 1 つも作成されていません",
"xpack.ml.settings.filterLists.table.notInUseAriaLabel": "使用されていません",
"xpack.ml.settings.filterLists.toolbar.deleteItemButtonLabel": "アイテムを削除",
"xpack.ml.settings.title": "設定",
"xpack.ml.settingsBreadcrumbLabel": "設定",
"xpack.ml.severitySelector.formControlAriaLabel": "重要度のしきい値を選択",
"xpack.ml.severitySelector.formControlLabel": "深刻度",
@ -29607,8 +29571,6 @@
"xpack.ml.suppliedConfigurations.preconfigurecJobsHeader": "提供された構成",
"xpack.ml.suppliedConfigurations.preconfigurecJobsHeaderDescription": "このページでは、関連するKibanaアセットと、事前定義された異常検知ジョブ構成の一覧が表示されます。",
"xpack.ml.suppliedConfigurations.suppliedConfigurations.docTitle": "提供された構成",
"xpack.ml.suppliedConfigurationsBreadcrumbs.suppliedConfigurationsLabel": "提供された構成",
"xpack.ml.suppliedConfigurationsLabel": "提供された構成",
"xpack.ml.swimlaneEmbeddable.errorMessage": "スイムレーンのデータを読み込めません",
"xpack.ml.swimlaneEmbeddable.noDataFound": "異常値が見つかりませんでした",
"xpack.ml.swimlaneEmbeddable.panelTitleLabel": "パネルタイトル",
@ -30111,7 +30073,6 @@
"xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.inputText": "テストするフレーズを入力",
"xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.label": "ゼロショット分類",
"xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel": "データドリフト",
"xpack.ml.trainedModelsBreadcrumbs.nodeOverviewLabel": "メモリー使用状況",
"xpack.ml.trainedModelsBreadcrumbs.trainedModelsLabel": "学習済みモデル",
"xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescription": "機械学習に関連したインデックスは現在アップグレード中です。",
"xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescriptionExtra": "現在いくつかのアクションが利用できません。",
@ -32404,23 +32365,13 @@
"xpack.observability.obltNav.infrastructure.metricsExplorer": "メトリックエクスプローラー",
"xpack.observability.obltNav.infrastructure.universalProfiling": "ユニバーサルプロファイリング",
"xpack.observability.obltNav.machineLearning": "機械学習",
"xpack.observability.obltNav.machineLearning.memoryUsage": "メモリー使用状況",
"xpack.observability.obltNav.management": "管理",
"xpack.observability.obltNav.ml.aiops_labs": "Aiops labs",
"xpack.observability.obltNav.ml.aiops_labs.change_point_detection": "変化点検出",
"xpack.observability.obltNav.ml.aiops_labs.log_pattern_analysis": "ログパターン分析",
"xpack.observability.obltNav.ml.aiops_labs.log_rate_analysis": "ログレート分析",
"xpack.observability.obltNav.ml.anomaly_detection": "異常検知",
"xpack.observability.obltNav.ml.anomaly_detection.jobs": "ジョブ",
"xpack.observability.obltNav.ml.data_frame_analytics": "データフレーム分析",
"xpack.observability.obltNav.ml.data_frame_analytics.jobs": "ジョブ",
"xpack.observability.obltNav.ml.data_visualizer": "データビジュアライザー",
"xpack.observability.obltNav.ml.data_visualizer.data_drift": "データドリフト",
"xpack.observability.obltNav.ml.data_visualizer.data_view_data_visualizer": "データビューデータビジュアライザー",
"xpack.observability.obltNav.ml.data_visualizer.esql_data_visualizer": "ES|QLデータビジュアライザー",
"xpack.observability.obltNav.ml.data_visualizer.file_data_visualizer": "ファイルデータビジュアライザー",
"xpack.observability.obltNav.ml.model_management": "モデル管理",
"xpack.observability.obltNav.ml.model_management.trainedModels": "学習済みモデル",
"xpack.observability.obltNav.otherTools": "その他のツール",
"xpack.observability.obltNav.otherTools.logsAnomalies": "Logs異常",
"xpack.observability.obltNav.otherTools.logsCategories": "Visualizeライブラリ",
@ -32840,20 +32791,6 @@
"xpack.observabilityShared.bottomBarActions.unsavedChanges": "{unsavedChangesCount, plural, other {# 未保存変更}}",
"xpack.observabilityShared.breadcrumbs.observabilityLinkText": "Observability",
"xpack.observabilityShared.common.constants.grouping": "Observability",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemDescription": "すべてのElastic機能に関する詳細なガイド",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLabel": "ドキュメントを参照",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLinkARIALabel": "すべてのElastic機能の詳細",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLinkLabel": "詳細",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemDescription": "Elasticのライブデモを見る",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemLabel": "デモ環境",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemLinkLabel": "デモの探索",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemDescription": "Elasticに関する意見を交換",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLabel": "フォーラムを探索",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLinkARIALabel": "Elasticディスカッションフォーラムを開く",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLinkLabel": "ディスカッションフォーラム",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemDescription": "ケースを作成してヘルプを依頼",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemLabel": "サポートハブ",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemLinkLabel": "サポートハブを開く",
"xpack.observabilityShared.featureFeedbackButton.tellUsWhatYouThinkLink": "ご意見をお聞かせください。",
"xpack.observabilityShared.fieldValueSelection.apply": "適用",
"xpack.observabilityShared.fieldValueSelection.apply.label": "{label}に選択したフィルターを適用",
@ -41847,21 +41784,12 @@
"xpack.serverlessObservability.nav.infrastructure": "インフラストラクチャー",
"xpack.serverlessObservability.nav.infrastructureInventory": "インフラインベントリ",
"xpack.serverlessObservability.nav.machineLearning": "機械学習",
"xpack.serverlessObservability.nav.machineLearning.memoryUsage": "メモリー使用状況",
"xpack.serverlessObservability.nav.ml.aiops_labs": "Aiops labs",
"xpack.serverlessObservability.nav.ml.aiops_labs.change_point_detection": "変化点検出",
"xpack.serverlessObservability.nav.ml.aiops_labs.log_pattern_analysis": "ログパターン分析",
"xpack.serverlessObservability.nav.ml.aiops_labs.log_rate_analysis": "ログレート分析",
"xpack.serverlessObservability.nav.ml.anomaly_detection": "異常検知",
"xpack.serverlessObservability.nav.ml.anomaly_detection.jobs": "ジョブ",
"xpack.serverlessObservability.nav.ml.data_frame_analytics": "データフレーム分析",
"xpack.serverlessObservability.nav.ml.data_frame_analytics.jobs": "ジョブ",
"xpack.serverlessObservability.nav.ml.data_visualizer": "データビジュアライザー",
"xpack.serverlessObservability.nav.ml.data_visualizer.data_drift": "データドリフト",
"xpack.serverlessObservability.nav.ml.data_visualizer.data_view_data_visualizer": "データビューデータビジュアライザー",
"xpack.serverlessObservability.nav.ml.data_visualizer.file_data_visualizer": "ファイルデータビジュアライザー",
"xpack.serverlessObservability.nav.ml.model_management": "モデル管理",
"xpack.serverlessObservability.nav.ml.model_management.trainedModels": "学習済みモデル",
"xpack.serverlessObservability.nav.mngt": "管理",
"xpack.serverlessObservability.nav.mngt.access": "アクセス",
"xpack.serverlessObservability.nav.mngt.alertsAndInsights": "アラートとインサイト",

View file

@ -27192,7 +27192,6 @@
"xpack.ml.anomalyDetectionAlert.name": "异常检测",
"xpack.ml.anomalyDetectionAlert.topNBucketsDescription": "为获取最高异常而要检查的最新存储桶数目。",
"xpack.ml.anomalyDetectionAlert.topNBucketsLabel": "最新存储桶数目",
"xpack.ml.anomalyDetectionBreadcrumbLabel": "异常检测",
"xpack.ml.anomalyExplorerPageLabel": "Anomaly Explorer",
"xpack.ml.anomalyResultsViewSelector.anomalyExplorerLabel": "在 Anomaly Explorer 中查看结果",
"xpack.ml.anomalyResultsViewSelector.buttonGroupLegend": "异常结果视图选择器",
@ -27330,7 +27329,6 @@
"xpack.ml.controls.selectSeverity.minorLabel": "轻微",
"xpack.ml.controls.selectSeverity.scoreDetailsDescription": "{value} 及以上分数",
"xpack.ml.controls.selectSeverity.warningLabel": "警告",
"xpack.ml.createJobsBreadcrumbLabel": "创建作业",
"xpack.ml.creationWizardUtils.destinationIndexInputAriaLabel": "选择唯一目标索引名称。",
"xpack.ml.creationWizardUtils.destinationIndexInvalidError": "目标索引名称无效。",
"xpack.ml.creationWizardUtils.destinationIndexInvalidErrorLink": "详细了解索引名称限制。",
@ -27897,7 +27895,6 @@
"xpack.ml.dataFrameAnalyticsBreadcrumbs.dataViewLabel": "数据视图",
"xpack.ml.dataFrameAnalyticsBreadcrumbs.esqlLabel": "索引数据可视化工具 (ES|QL)",
"xpack.ml.dataFrameAnalyticsBreadcrumbs.jobsManagementLabel": "作业",
"xpack.ml.dataFrameAnalyticsLabel": "数据帧分析",
"xpack.ml.dataGrid.CcsWarningCalloutBody": "检索数据视图的数据时出现问题。源预览和跨集群搜索仅在 7.10 及以上版本上受支持。可能需要配置和创建转换。",
"xpack.ml.dataGrid.CcsWarningCalloutTitle": "跨集群搜索未返回字段数据。",
"xpack.ml.dataGrid.columnChart.ErrorMessageToast": "提取直方图数据时发生错误:{error}",
@ -27933,7 +27930,6 @@
"xpack.ml.datavisualizer.selector.dataVisualizerDescription": "Machine Learning 数据可视化工具通过分析日志文件或现有 Elasticsearch 索引中的指标和字段,帮助您理解数据。",
"xpack.ml.datavisualizer.selector.dataVisualizerTitle": "数据可视化工具",
"xpack.ml.datavisualizer.selector.esqlTechnicalPreviewBadge.titleMsg": "ES|QL 数据可视化工具处于技术预览状态。",
"xpack.ml.datavisualizer.selector.importDataDescription": "从日志文件导入数据。您可以上传不超过 {maxFileSize} 的文件。",
"xpack.ml.datavisualizer.selector.importDataTitle": "可视化来自文件的数据",
"xpack.ml.datavisualizer.selector.selectDataViewButtonLabel": "选择数据视图",
"xpack.ml.datavisualizer.selector.selectDataViewTitle": "可视化来自数据视图的数据",
@ -27965,27 +27961,21 @@
"xpack.ml.deepLink.analyticsMap": "分析地图",
"xpack.ml.deepLink.anomalyDetection": "异常检测",
"xpack.ml.deepLink.anomalyExplorer": "Anomaly Explorer",
"xpack.ml.deepLink.calendarSettings": "日历",
"xpack.ml.deepLink.changePointDetection": "更改点检测",
"xpack.ml.deepLink.dataDrift": "数据偏移",
"xpack.ml.deepLink.dataFrameAnalytics": "数据帧分析",
"xpack.ml.deepLink.dataVisualizer": "数据可视化工具",
"xpack.ml.deepLink.esqlDataVisualizer": "ES|QL 数据可视化工具",
"xpack.ml.deepLink.fileUpload": "文件上传",
"xpack.ml.deepLink.filterListsSettings": "筛选列表",
"xpack.ml.deepLink.indexDataVisualizer": "索引数据可视化工具",
"xpack.ml.deepLink.logPatternAnalysis": "日志模式分析",
"xpack.ml.deepLink.logRateAnalysis": "日志速率分析",
"xpack.ml.deepLink.memoryUsage": "内存利用率",
"xpack.ml.deepLink.modelManagement": "模型管理",
"xpack.ml.deepLink.nodes": "节点",
"xpack.ml.deepLink.notifications": "通知",
"xpack.ml.deepLink.overview": "概览",
"xpack.ml.deepLink.resultExplorer": "结果浏览器",
"xpack.ml.deepLink.settings": "设置",
"xpack.ml.deepLink.singleMetricViewer": "Single Metric Viewer",
"xpack.ml.deepLink.suppliedConfigurations": "提供的配置",
"xpack.ml.deepLink.trainedModels": "已训练模型",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanDelete.job": "继续删除 {length, plural, other {# 个作业}}",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanDelete.model": "继续删除 {length, plural, other {# 个模型}}",
"xpack.ml.deleteSpaceAwareItemCheckModal.buttonTextCanUnTagConfirm": "从当前工作区中移除",
@ -28208,11 +28198,6 @@
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingCalendarsButton": "使用日历的作业",
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingFiltersAria": "使用筛选列表的作业",
"xpack.ml.importExport.exportFlyout.exportJobDependenciesWarningCallout.jobUsingFiltersButton": "使用筛选列表的作业",
"xpack.ml.importExport.exportFlyout.flyoutHeader": "导出作业",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.cancelButton": "取消",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.confirmButton": "确认",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.text": "更改选项卡将会清除当前选定的作业",
"xpack.ml.importExport.exportFlyout.switchTabsConfirm.title": "更改选项卡?",
"xpack.ml.importExport.importButton": "导入作业",
"xpack.ml.importExport.importFlyout.cannotImportJobCallout.jobListAria": "查看作业",
"xpack.ml.importExport.importFlyout.cannotImportJobCallout.jobListButton": "查看作业",
@ -28626,7 +28611,6 @@
"xpack.ml.management.jobsList.noPermissionToAccessLabel": "访问被拒绝",
"xpack.ml.management.jobsList.syncFlyoutButton": "同步已保存对象",
"xpack.ml.management.jobsList.trainedModelsDocsLabel": "已训练模型文档",
"xpack.ml.management.jobsListTitle": "Machine Learning",
"xpack.ml.management.jobsSpacesList.jobObjectNoun": "作业",
"xpack.ml.management.jobsSpacesList.modelObjectNoun": "已训练模型",
"xpack.ml.management.jobsSpacesList.updateSpaces.error": "更新 {id} 时出错",
@ -28698,9 +28682,6 @@
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.createJobButtonText": "创建作业",
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.createJobMessage": "创建异常检测作业",
"xpack.ml.mapsAnomaliesLayerEmptyPrompt.emptyPromptText": "通过异常检测,可发现地理数据中的异常行为。创建使用 lat_long 函数的作业,地图异常图层需要这样做。",
"xpack.ml.memoryUsage.memoryTab": "内存使用",
"xpack.ml.memoryUsage.memoryUsageHeader": "内存利用率",
"xpack.ml.memoryUsage.nodesTab": "节点",
"xpack.ml.memoryUsage.treeMap.adLabel": "异常检测作业",
"xpack.ml.memoryUsage.treeMap.dfaLabel": "数据帧分析作业",
"xpack.ml.memoryUsage.treeMap.emptyPrompt": "没有打开的作业或已训练模型匹配当前选择。",
@ -28711,10 +28692,8 @@
"xpack.ml.mlEntitySelector.dfaOptionsLabel": "数据帧分析",
"xpack.ml.mlEntitySelector.fetchError": "无法提取 ML 实体",
"xpack.ml.mlEntitySelector.trainedModelsLabel": "已训练模型",
"xpack.ml.modelManagement.memoryUsage.docTitle": "内存利用率",
"xpack.ml.modelManagement.trainedModels.docTitle": "已训练模型",
"xpack.ml.modelManagement.trainedModelsHeader": "已训练模型",
"xpack.ml.modelManagementLabel": "模型管理",
"xpack.ml.models.dfaValidation.messages.analysisFieldsEmptyWarningText": "分析包括的一些字段至少有 {percentEmpty}% 的空值,可能不适合分析。",
"xpack.ml.models.dfaValidation.messages.analysisFieldsHeading": "分析字段",
"xpack.ml.models.dfaValidation.messages.analysisFieldsHighWarningText": "已选择 {includedFieldsThreshold} 以上字段进行分析。这可能导致资源使用率增加以及作业长时间运行。",
@ -28883,31 +28862,21 @@
"xpack.ml.multiSelectPicker.NoFiltersFoundMessage": "未找到任何筛选",
"xpack.ml.navMenu.aiopsTabLinkText": "AIOps 实验室",
"xpack.ml.navMenu.anomalyDetection.anomalyExplorerText": "Anomaly Explorer",
"xpack.ml.navMenu.anomalyDetection.jobsManagementText": "作业",
"xpack.ml.navMenu.anomalyDetection.singleMetricViewerText": "Single Metric Viewer",
"xpack.ml.navMenu.anomalyDetection.suppliedConfigurationsLinkText": "提供的配置",
"xpack.ml.navMenu.anomalyDetectionTabLinkText": "异常检测",
"xpack.ml.navMenu.changePointDetectionLinkText": "更改点检测",
"xpack.ml.navMenu.dataComparisonText": "数据偏移",
"xpack.ml.navMenu.dataFrameAnalytics.analyticsMapText": "分析地图",
"xpack.ml.navMenu.dataFrameAnalytics.jobsManagementText": "作业",
"xpack.ml.navMenu.dataFrameAnalytics.resultsExplorerText": "结果浏览器",
"xpack.ml.navMenu.dataFrameAnalyticsTabLinkText": "数据帧分析",
"xpack.ml.navMenu.dataViewDataVisualizerLinkText": "数据视图",
"xpack.ml.navMenu.dataVisualizerTabLinkText": "数据可视化工具",
"xpack.ml.navMenu.esqlDataVisualizerLinkText": "ES|QL",
"xpack.ml.navMenu.fileDataVisualizerLinkText": "文件",
"xpack.ml.navMenu.logCategorizationLinkText": "日志模式分析",
"xpack.ml.navMenu.logRateAnalysisLinkText": "日志速率分析",
"xpack.ml.navMenu.memoryUsageText": "内存利用率",
"xpack.ml.navMenu.mlAppNameText": "Machine Learning 和分析",
"xpack.ml.navMenu.modelManagementText": "模型管理",
"xpack.ml.navMenu.notificationsTabLinkText": "通知",
"xpack.ml.navMenu.overviewTabLinkText": "概览",
"xpack.ml.navMenu.settingsTabLinkText": "设置",
"xpack.ml.navMenu.trainedModelsTabBetaLabel": "技术预览",
"xpack.ml.navMenu.trainedModelsTabBetaTooltipContent": "此功能处于技术预览状态在未来版本中可能会更改或完全移除。Elastic 将努力修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。",
"xpack.ml.navMenu.trainedModelsText": "已训练模型",
"xpack.ml.newJob.fromGeo.createJob.error.noTimeRange": "未指定时间范围。",
"xpack.ml.newJob.fromLens.createJob.error.colsNoSourceField": "某些列不包含源字段。",
"xpack.ml.newJob.fromLens.createJob.error.colsUsingFilterTimeSift": "列包含与 ML 检测工具不兼容的设置,不支持时间偏移和筛选依据。",
@ -29412,7 +29381,6 @@
"xpack.ml.overview.nodesPanel.header": "节点",
"xpack.ml.overview.nodesPanel.totalNodesLabel": "合计",
"xpack.ml.overview.nodesPanel.viewNodeLink": "查看节点",
"xpack.ml.overview.notificationsLabel": "通知",
"xpack.ml.overview.overviewLabel": "概览",
"xpack.ml.overview.statsBar.failedAnalyticsLabel": "失败",
"xpack.ml.overview.statsBar.runningAnalyticsLabel": "正在运行",
@ -29541,7 +29509,6 @@
"xpack.ml.sampleDataLinkLabel": "ML 作业",
"xpack.ml.savedObjectFinder.createADataView": "创建数据视图",
"xpack.ml.selectDataViewLabel": "选择数据视图",
"xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "异常检测",
"xpack.ml.settings.anomalyDetection.calendarsDstSummaryCount": "您有 {calendarsCountBadge} 个{calendarsDstCount, plural, other {日历}}",
"xpack.ml.settings.anomalyDetection.calendarsDstText": "DST 日历包含您不希望为其生成异常的已计划事件列表,考虑可能导致事件提前或延后一小时发生的夏令时转换。",
"xpack.ml.settings.anomalyDetection.calendarsDstTitle": "DST 日历",
@ -29561,11 +29528,9 @@
"xpack.ml.settings.anomalyDetection.manageFilterListsLink": "管理",
"xpack.ml.settings.breadcrumbs.calendarManagement.createLabel": "创建",
"xpack.ml.settings.breadcrumbs.calendarManagement.editLabel": "编辑",
"xpack.ml.settings.breadcrumbs.calendarManagementLabel": "日历 DST 管理",
"xpack.ml.settings.breadcrumbs.dataComparisonLabel": "数据偏移",
"xpack.ml.settings.breadcrumbs.filterLists.createLabel": "创建",
"xpack.ml.settings.breadcrumbs.filterLists.editLabel": "编辑",
"xpack.ml.settings.breadcrumbs.filterListsLabel": "筛选列表",
"xpack.ml.settings.calendarList.docTitle": "日历",
"xpack.ml.settings.calendars.listHeader.calendarsDescription": "日志包含不应生成异常的已计划事件列表,例如已计划系统中断或公共假期。同一日历可分配给多个作业。{br}{learnMoreLink}",
"xpack.ml.settings.calendars.listHeader.calendarsDescription.learnMoreLinkText": "了解详情",
@ -29625,7 +29590,6 @@
"xpack.ml.settings.filterLists.table.noFiltersCreatedTitle": "未创建任何筛选",
"xpack.ml.settings.filterLists.table.notInUseAriaLabel": "未在使用中",
"xpack.ml.settings.filterLists.toolbar.deleteItemButtonLabel": "删除项",
"xpack.ml.settings.title": "设置",
"xpack.ml.settingsBreadcrumbLabel": "设置",
"xpack.ml.severitySelector.formControlAriaLabel": "选择严重性阈值",
"xpack.ml.severitySelector.formControlLabel": "严重性",
@ -29660,8 +29624,6 @@
"xpack.ml.suppliedConfigurations.preconfigurecJobsHeader": "提供的配置",
"xpack.ml.suppliedConfigurations.preconfigurecJobsHeaderDescription": "此页列出了预定义的异常检测作业配置和相关 Kibana 资产。",
"xpack.ml.suppliedConfigurations.suppliedConfigurations.docTitle": "提供的配置",
"xpack.ml.suppliedConfigurationsBreadcrumbs.suppliedConfigurationsLabel": "提供的配置",
"xpack.ml.suppliedConfigurationsLabel": "提供的配置",
"xpack.ml.swimlaneEmbeddable.errorMessage": "无法为泳道加载数据",
"xpack.ml.swimlaneEmbeddable.noDataFound": "找不到异常",
"xpack.ml.swimlaneEmbeddable.panelTitleLabel": "面板标题",
@ -30164,7 +30126,6 @@
"xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.inputText": "输入短语以进行测试",
"xpack.ml.trainedModels.testModelsFlyout.zeroShotClassification.label": "Zero shot 分类",
"xpack.ml.trainedModelsBreadcrumbs.dataDriftLabel": "数据偏移",
"xpack.ml.trainedModelsBreadcrumbs.nodeOverviewLabel": "内存利用率",
"xpack.ml.trainedModelsBreadcrumbs.trainedModelsLabel": "已训练模型",
"xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescription": "当前正在升级与 Machine Learning 相关的索引。",
"xpack.ml.upgrade.upgradeWarning.upgradeInProgressWarningDescriptionExtra": "此次某些操作不可用。",
@ -32459,23 +32420,13 @@
"xpack.observability.obltNav.infrastructure.metricsExplorer": "指标浏览器",
"xpack.observability.obltNav.infrastructure.universalProfiling": "Universal Profiling",
"xpack.observability.obltNav.machineLearning": "Machine Learning",
"xpack.observability.obltNav.machineLearning.memoryUsage": "内存使用",
"xpack.observability.obltNav.management": "管理",
"xpack.observability.obltNav.ml.aiops_labs": "Aiops 实验室",
"xpack.observability.obltNav.ml.aiops_labs.change_point_detection": "更改点检测",
"xpack.observability.obltNav.ml.aiops_labs.log_pattern_analysis": "日志模式分析",
"xpack.observability.obltNav.ml.aiops_labs.log_rate_analysis": "日志速率分析",
"xpack.observability.obltNav.ml.anomaly_detection": "异常检测",
"xpack.observability.obltNav.ml.anomaly_detection.jobs": "作业",
"xpack.observability.obltNav.ml.data_frame_analytics": "数据帧分析",
"xpack.observability.obltNav.ml.data_frame_analytics.jobs": "作业",
"xpack.observability.obltNav.ml.data_visualizer": "数据可视化工具",
"xpack.observability.obltNav.ml.data_visualizer.data_drift": "数据偏移",
"xpack.observability.obltNav.ml.data_visualizer.data_view_data_visualizer": "数据视图数据可视化工具",
"xpack.observability.obltNav.ml.data_visualizer.esql_data_visualizer": "ES|QL 数据可视化工具",
"xpack.observability.obltNav.ml.data_visualizer.file_data_visualizer": "文件数据可视化工具",
"xpack.observability.obltNav.ml.model_management": "模型管理",
"xpack.observability.obltNav.ml.model_management.trainedModels": "已训练模型",
"xpack.observability.obltNav.otherTools": "其他工具",
"xpack.observability.obltNav.otherTools.logsAnomalies": "日志异常",
"xpack.observability.obltNav.otherTools.logsCategories": "Visualize 库",
@ -32896,20 +32847,6 @@
"xpack.observabilityShared.bottomBarActions.unsavedChanges": "{unsavedChangesCount, plural, =0{0 个未保存更改} one {1 个未保存更改} other {# 个未保存更改}}",
"xpack.observabilityShared.breadcrumbs.observabilityLinkText": "Observability",
"xpack.observabilityShared.common.constants.grouping": "Observability",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemDescription": "有关所有 Elastic 功能的深入指南",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLabel": "浏览文档",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLinkARIALabel": "详细了解所有 Elastic 功能",
"xpack.observabilityShared.experimentalOnboardingFlow.browseDocumentationFlexItemLinkLabel": "了解详情",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemDescription": "浏览我们的实时演示环境",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemLabel": "演示环境",
"xpack.observabilityShared.experimentalOnboardingFlow.demoEnvironmentFlexItemLinkLabel": "浏览演示",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemDescription": "交流有关 Elastic 的看法",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLabel": "浏览论坛",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLinkARIALabel": "打开 Elastic 讨论论坛",
"xpack.observabilityShared.experimentalOnboardingFlow.exploreForumFlexItemLinkLabel": "讨论论坛",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemDescription": "通过创建案例获取帮助",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemLabel": "支持中心",
"xpack.observabilityShared.experimentalOnboardingFlow.supportHubFlexItemLinkLabel": "打开支持中心",
"xpack.observabilityShared.featureFeedbackButton.tellUsWhatYouThinkLink": "告诉我们您的看法!",
"xpack.observabilityShared.fieldValueSelection.apply": "应用",
"xpack.observabilityShared.fieldValueSelection.apply.label": "为 {label} 应用选定筛选",
@ -41911,21 +41848,12 @@
"xpack.serverlessObservability.nav.infrastructure": "基础设施",
"xpack.serverlessObservability.nav.infrastructureInventory": "基础设施库存",
"xpack.serverlessObservability.nav.machineLearning": "Machine Learning",
"xpack.serverlessObservability.nav.machineLearning.memoryUsage": "内存使用",
"xpack.serverlessObservability.nav.ml.aiops_labs": "Aiops 实验室",
"xpack.serverlessObservability.nav.ml.aiops_labs.change_point_detection": "更改点检测",
"xpack.serverlessObservability.nav.ml.aiops_labs.log_pattern_analysis": "日志模式分析",
"xpack.serverlessObservability.nav.ml.aiops_labs.log_rate_analysis": "日志速率分析",
"xpack.serverlessObservability.nav.ml.anomaly_detection": "异常检测",
"xpack.serverlessObservability.nav.ml.anomaly_detection.jobs": "作业",
"xpack.serverlessObservability.nav.ml.data_frame_analytics": "数据帧分析",
"xpack.serverlessObservability.nav.ml.data_frame_analytics.jobs": "作业",
"xpack.serverlessObservability.nav.ml.data_visualizer": "数据可视化工具",
"xpack.serverlessObservability.nav.ml.data_visualizer.data_drift": "数据偏移",
"xpack.serverlessObservability.nav.ml.data_visualizer.data_view_data_visualizer": "数据视图数据可视化工具",
"xpack.serverlessObservability.nav.ml.data_visualizer.file_data_visualizer": "文件数据可视化工具",
"xpack.serverlessObservability.nav.ml.model_management": "模型管理",
"xpack.serverlessObservability.nav.ml.model_management.trainedModels": "已训练模型",
"xpack.serverlessObservability.nav.mngt": "管理",
"xpack.serverlessObservability.nav.mngt.access": "访问",
"xpack.serverlessObservability.nav.mngt.alertsAndInsights": "告警和洞见",

View file

@ -35,7 +35,7 @@ const getKibanaLinkForESAsset = (type: ElasticsearchAssetType, id: string): stri
case 'transform':
return `/app/management/data/transform?_a=(transform:(queryText:${id}))`;
case 'ml_model':
return `/app/ml/trained_models?_a=(trained_models:(queryText:'model_id:(${id})'))`;
return `/app/management/ml/trained_models?_a=(trained_models:(queryText:'model_id:(${id})'))`;
default:
return '';
}

View file

@ -14,6 +14,7 @@ export const ML_APP_NAME = i18n.translate('xpack.ml.navMenu.mlAppNameText', {
defaultMessage: 'Machine Learning and Analytics',
});
export const ML_APP_ROUTE = '/app/ml';
export const ML_MANAGEMENT_APP_ROUTE = '/app/management/ml';
export const ML_INTERNAL_BASE_PATH = '/internal/ml';
export const ML_EXTERNAL_BASE_PATH = '/api/ml';

View file

@ -7,11 +7,34 @@
export const ML_APP_LOCATOR = 'ML_APP_LOCATOR';
/**
* @deprecated since 9.1, kept here to redirect old bookmarks
*/
export const DEPRECATED_ML_ROUTE_TO_NEW_ROUTE = {
jobs: 'anomaly_detection',
data_frame_analytics: 'analytics',
trained_models: 'trained_models',
notifications: 'overview?_g=(tab:notifications)&',
memory_usage: 'overview',
supplied_configurations: 'anomaly_detection/ad_supplied_configurations',
settings: 'ad_settings',
'settings/calendars_list': 'ad_settings/calendars_list',
'settings/calendars_list/new_calendar': 'ad_settings/calendars_list/new_calendar',
'settings/calendars_dst_list': 'ad_settings/calendars_dst_list',
'settings/calendars_dst_list/new_calendar': 'ad_settings/calendars_dst_list/new_calendar',
'settings/filter_lists': 'ad_settings/filter_lists',
'settings/filter_lists/new_filter_list': 'ad_settings/filter_lists/new_filter_list',
nodes: 'overview',
'jobs/new_job/step/index_or_search': 'anomaly_detection/jobs/new_job/step/select_source',
};
export const ML_PAGES = {
ANOMALY_DETECTION_JOBS_MANAGE: 'jobs',
ANOMALY_DETECTION_JOBS_MANAGE: '',
ANOMALY_DETECTION_JOBS_MANAGE_FOR_URL: 'jobs',
ANOMALY_EXPLORER: 'explorer',
SINGLE_METRIC_VIEWER: 'timeseriesexplorer',
DATA_FRAME_ANALYTICS_JOBS_MANAGE: 'data_frame_analytics',
DATA_FRAME_ANALYTICS_JOBS_MANAGE: '',
DATA_FRAME_ANALYTICS_JOBS_MANAGE_FOR_URL: 'data_frame_analytics',
DATA_FRAME_ANALYTICS_SOURCE_SELECTION: 'data_frame_analytics/source_selection',
DATA_FRAME_ANALYTICS_CREATE_JOB: 'data_frame_analytics/new_job',
TRAINED_MODELS_MANAGE: 'trained_models',
@ -22,7 +45,7 @@ export const ML_PAGES = {
MEMORY_USAGE: 'memory_usage',
DATA_FRAME_ANALYTICS_EXPLORATION: 'data_frame_analytics/exploration',
DATA_FRAME_ANALYTICS_MAP: 'data_frame_analytics/map',
SUPPLIED_CONFIGURATIONS: 'supplied_configurations',
SUPPLIED_CONFIGURATIONS: 'ad_supplied_configurations',
/**
* Page: Data Visualizer
*/
@ -53,23 +76,23 @@ export const ML_PAGES = {
ANOMALY_DETECTION_CREATE_JOB_CATEGORIZATION: 'jobs/new_job/categorization',
ANOMALY_DETECTION_CREATE_JOB_RARE: 'jobs/new_job/rare',
ANOMALY_DETECTION_CREATE_JOB_GEO: 'jobs/new_job/geo',
ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED: 'jobs/new_job/convert_to_advanced',
ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED: 'jobs/new_job/advanced',
ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: 'jobs/new_job/step/job_type',
ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: 'jobs/new_job/step/index_or_search',
ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: 'jobs/new_job/step/select_source',
ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: 'jobs/new_job/from_lens',
ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS: 'jobs/new_job/from_pattern_analysis',
ANOMALY_DETECTION_CREATE_JOB_FROM_MAP: 'jobs/new_job/from_map',
ANOMALY_DETECTION_MODULES_VIEW_OR_CREATE: 'modules/check_view_or_create',
SETTINGS: 'settings',
CALENDARS_MANAGE: 'settings/calendars_list',
CALENDARS_DST_MANAGE: 'settings/calendars_dst_list',
CALENDARS_NEW: 'settings/calendars_list/new_calendar',
CALENDARS_DST_NEW: 'settings/calendars_dst_list/new_calendar',
CALENDARS_EDIT: 'settings/calendars_list/edit_calendar',
CALENDARS_DST_EDIT: 'settings/calendars_dst_list/edit_calendar',
FILTER_LISTS_MANAGE: 'settings/filter_lists',
FILTER_LISTS_NEW: 'settings/filter_lists/new_filter_list',
FILTER_LISTS_EDIT: 'settings/filter_lists/edit_filter_list',
SETTINGS: '',
CALENDARS_MANAGE: 'calendars_list',
CALENDARS_DST_MANAGE: 'calendars_dst_list',
CALENDARS_NEW: 'calendars_list/new_calendar',
CALENDARS_DST_NEW: 'calendars_dst_list/new_calendar',
CALENDARS_EDIT: 'calendars_list/edit_calendar',
CALENDARS_DST_EDIT: 'calendars_dst_list/edit_calendar',
FILTER_LISTS_MANAGE: 'filter_lists',
FILTER_LISTS_NEW: 'filter_lists/new_filter_list',
FILTER_LISTS_EDIT: 'filter_lists/edit_filter_list',
OVERVIEW: 'overview',
NOTIFICATIONS: 'notifications',
AIOPS: 'aiops',

View file

@ -15,7 +15,7 @@ import type { ListingPageUrlState } from '@kbn/ml-url-state';
import type { JobId } from './anomaly_detection_jobs/job';
import type { ML_PAGES } from '../constants/locator';
type OptionalPageState = object | undefined;
type OptionalPageState = (object & { globalState?: MlCommonGlobalState }) | undefined;
export type MLPageState<PageType, PageState> = PageState extends OptionalPageState
? { page: PageType; pageState?: PageState }
@ -43,8 +43,14 @@ export interface MlGenericUrlPageState extends MlIndexBasedSearchState {
}
export type MlGenericUrlState = MLPageState<
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER
| typeof ML_PAGES.DATA_VISUALIZER_ESQL
| typeof ML_PAGES.AIOPS
| typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION
| typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT
| typeof ML_PAGES.AIOPS_LOG_RATE_ANALYSIS
| typeof ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT
| typeof ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT
| typeof ML_PAGES.AIOPS_CHANGE_POINT_DETECTION
| typeof ML_PAGES.ANOMALY_EXPLORER
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED
@ -53,29 +59,26 @@ export type MlGenericUrlState = MLPageState<
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX
| typeof ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB
| typeof ML_PAGES.OVERVIEW
| typeof ML_PAGES.CALENDARS_MANAGE
| typeof ML_PAGES.CALENDARS_DST_MANAGE
| typeof ML_PAGES.CALENDARS_NEW
| typeof ML_PAGES.CALENDARS_DST_NEW
| typeof ML_PAGES.FILTER_LISTS_MANAGE
| typeof ML_PAGES.FILTER_LISTS_NEW
| typeof ML_PAGES.SETTINGS
| typeof ML_PAGES.CALENDARS_MANAGE
| typeof ML_PAGES.CALENDARS_NEW
| typeof ML_PAGES.DATA_DRIFT_CUSTOM
| typeof ML_PAGES.DATA_DRIFT_INDEX_SELECT
| typeof ML_PAGES.DATA_DRIFT
| typeof ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB
| typeof ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION
| typeof ML_PAGES.DATA_VISUALIZER
| typeof ML_PAGES.DATA_VISUALIZER_FILE
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_SELECT
| typeof ML_PAGES.AIOPS
| typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION
| typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT
| typeof ML_PAGES.AIOPS_LOG_RATE_ANALYSIS
| typeof ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT
| typeof ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT
| typeof ML_PAGES.AIOPS_CHANGE_POINT_DETECTION
| typeof ML_PAGES.SUPPLIED_CONFIGURATIONS,
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER
| typeof ML_PAGES.DATA_VISUALIZER_ESQL
| typeof ML_PAGES.FILTER_LISTS_MANAGE
| typeof ML_PAGES.FILTER_LISTS_NEW
| typeof ML_PAGES.SETTINGS
| typeof ML_PAGES.SINGLE_METRIC_VIEWER
| typeof ML_PAGES.SUPPLIED_CONFIGURATIONS
| typeof ML_PAGES.OVERVIEW,
MlGenericUrlPageState | undefined
>;
export interface AnomalyDetectionQueryState {

View file

@ -16,6 +16,7 @@ export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference';
export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels';
export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt';
export const ML_OVERVIEW_PANELS = 'ml.overviewPanels';
export const ML_OVERVIEW_PANELS_EXTENDED = 'ml.overviewPanelsExtended';
export const ML_ELSER_CALLOUT_DISMISSED = 'ml.elserUpdateCalloutDismissed';
export const ML_SCHEDULED_MODEL_DEPLOYMENTS = 'ml.trainedModels.scheduledModelDeployments';
@ -63,6 +64,10 @@ export interface OverviewPanelsState {
dfaJobs: boolean;
}
export interface OverviewPanelsExtendedState {
memoryUsage: boolean;
}
export interface MlStorageRecord {
[key: string]: unknown;
[ML_ENTITY_FIELDS_CONFIG]: PartitionFieldsConfig;
@ -72,6 +77,7 @@ export interface MlStorageRecord {
[ML_ANOMALY_EXPLORER_PANELS]: AnomalyExplorerPanelsState | undefined;
[ML_NOTIFICATIONS_LAST_CHECKED_AT]: number | undefined;
[ML_OVERVIEW_PANELS]: OverviewPanelsState;
[ML_OVERVIEW_PANELS_EXTENDED]: OverviewPanelsExtendedState;
[ML_ELSER_CALLOUT_DISMISSED]: boolean | undefined;
[ML_SCHEDULED_MODEL_DEPLOYMENTS]: StartAllocationParams[];
}
@ -94,6 +100,8 @@ export type TMlStorageMapped<T extends MlStorageKey> = T extends typeof ML_ENTIT
? number | undefined
: T extends typeof ML_OVERVIEW_PANELS
? OverviewPanelsState | undefined
: T extends typeof ML_OVERVIEW_PANELS_EXTENDED
? OverviewPanelsExtendedState | undefined
: T extends typeof ML_ELSER_CALLOUT_DISMISSED
? boolean | undefined
: T extends typeof ML_SCHEDULED_MODEL_DEPLOYMENTS
@ -108,6 +116,7 @@ export const ML_STORAGE_KEYS = [
ML_ANOMALY_EXPLORER_PANELS,
ML_NOTIFICATIONS_LAST_CHECKED_AT,
ML_OVERVIEW_PANELS,
ML_OVERVIEW_PANELS_EXTENDED,
ML_ELSER_CALLOUT_DISMISSED,
ML_SCHEDULED_MODEL_DEPLOYMENTS,
] as const;

View file

@ -64,19 +64,21 @@ const MlAnomalyAlertTrigger: FC<MlAnomalyAlertTriggerProps> = ({
if (!mlCapabilities.canCreateJob) return;
getStartServices().then((startServices) => {
const locator = startServices[2].locator;
if (!locator) return;
locator.getUrl({ page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB }).then((url) => {
if (mounted) {
setNewJobUrl(url);
}
});
const { managementLocator } = startServices[2];
if (!managementLocator) return;
managementLocator
.getUrl({ page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB }, 'anomaly_detection')
.then(({ url }) => {
if (mounted) {
setNewJobUrl(url);
}
});
});
return () => {
mounted = false;
};
}, [getStartServices, mlCapabilities]);
}, [mlCapabilities, getStartServices]);
const mlHttpService = useMemo(() => new HttpService(http!), [http]);
const adJobsApiService = useMemo(() => jobsApiProvider(mlHttpService), [mlHttpService]);

View file

@ -30,6 +30,7 @@ export const AccessDeniedCallout: FC<AccessDeniedCalloutProps> = ({ missingCapab
return (
<>
<EuiPageTemplate.EmptyPrompt
data-test-subj="mlAccessDenied"
color={'danger'}
alignment={'horizontalCenter'}
iconType="warning"

View file

@ -18,6 +18,8 @@ import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { StorageContextProvider } from '@kbn/ml-local-storage';
import useLifecycles from 'react-use/lib/useLifecycles';
import useObservable from 'react-use/lib/useObservable';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { ExperimentalFeatures, MlFeatures, NLPSettings } from '../../common/constants/app';
import { ML_STORAGE_KEYS } from '../../common/types/storage';
import type { MlSetupDependencies, MlStartDependencies } from '../plugin';
@ -28,6 +30,7 @@ import { EnabledFeaturesContextProvider, MlServerInfoContextProvider } from './c
import type { StartServices } from './contexts/kibana';
import { getMlGlobalServices } from './util/get_services';
import { MlTelemetryContextProvider } from './contexts/ml/ml_telemetry_context';
import type { ManagementSectionId } from './management';
export type MlDependencies = Omit<
MlSetupDependencies,
@ -38,11 +41,12 @@ export type MlDependencies = Omit<
interface AppProps {
coreStart: CoreStart;
deps: MlDependencies;
appMountParams: AppMountParameters;
appMountParams: ManagementAppMountParams | AppMountParameters;
isServerless: boolean;
mlFeatures: MlFeatures;
experimentalFeatures: ExperimentalFeatures;
nlpSettings: NLPSettings;
entryPoint?: ManagementSectionId;
}
const localStorage = new Storage(window.localStorage);
@ -53,7 +57,7 @@ export interface MlServicesContext {
export type MlGlobalServices = ReturnType<typeof getMlGlobalServices>;
const App: FC<AppProps> = ({
export const App: FC<AppProps> = ({
coreStart,
deps,
appMountParams,
@ -61,10 +65,13 @@ const App: FC<AppProps> = ({
mlFeatures,
experimentalFeatures,
nlpSettings,
entryPoint,
}) => {
const pageDeps: PageDependencies = {
history: appMountParams.history,
setHeaderActionMenu: appMountParams.setHeaderActionMenu,
setHeaderActionMenu: isPopulatedObject(appMountParams, ['setHeaderActionMenu'])
? appMountParams.setHeaderActionMenu
: undefined,
setBreadcrumbs: coreStart.chrome!.setBreadcrumbs,
};
@ -156,7 +163,7 @@ const App: FC<AppProps> = ({
>
<MlServerInfoContextProvider nlpSettings={nlpSettings}>
<MlTelemetryContextProvider telemetryClient={deps.telemetry}>
<MlRouter pageDeps={pageDeps} />
<MlRouter pageDeps={pageDeps} entryPoint={entryPoint} />
</MlTelemetryContextProvider>
</MlServerInfoContextProvider>
</EnabledFeaturesContextProvider>

View file

@ -26,6 +26,7 @@ export interface CollapsiblePanelProps {
header: React.ReactElement;
headerItems?: React.ReactElement[];
ariaLabel: string;
dataTestSubj?: string;
}
export const CollapsiblePanel: FC<PropsWithChildren<CollapsiblePanelProps>> = ({
@ -35,11 +36,13 @@ export const CollapsiblePanel: FC<PropsWithChildren<CollapsiblePanelProps>> = ({
header,
headerItems,
ariaLabel,
dataTestSubj,
}) => {
const { euiTheme } = useEuiTheme();
return (
<EuiSplitPanel.Outer
data-test-subj={dataTestSubj}
grow
hasShadow={false}
css={{
@ -100,7 +103,7 @@ export const CollapsiblePanel: FC<PropsWithChildren<CollapsiblePanelProps>> = ({
export interface StatEntry {
label: string;
value: number;
value: number | string;
'data-test-subj'?: string;
}

View file

@ -10,16 +10,22 @@ import PropTypes from 'prop-types';
import { EuiIcon, EuiFlexItem } from '@elastic/eui';
import { LinkCard } from '../link_card';
import { useMlKibana } from '../../contexts/kibana';
import { useMlManagementLocator } from '../../contexts/kibana';
import { ML_PAGES } from '../../../../common/constants/locator';
export const RecognizedResult = ({ config, indexPattern, savedSearch }) => {
const {
services: {
http: { basePath },
},
} = useMlKibana();
const id = savedSearch === null ? `index=${indexPattern.id}` : `savedSearchId=${savedSearch.id}`;
const href = `${basePath.get()}/app/ml/jobs/new_job/recognize?id=${config.id}&${id}`;
const mlManagementLocator = useMlManagementLocator();
const navigateToCreateJobRecognizerPath = async () => {
if (!mlManagementLocator) return;
await mlManagementLocator.navigate({
sectionId: 'ml',
appId: `anomaly_detection/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER}?id=${config.id}&${id}`,
});
};
let logo = null;
// if a logo is available, use that, otherwise display the id
@ -36,7 +42,7 @@ export const RecognizedResult = ({ config, indexPattern, savedSearch }) => {
<EuiFlexItem>
<LinkCard
data-test-subj={`mlRecognizerCard ${config.id}`}
href={href}
onClick={navigateToCreateJobRecognizerPath}
title={config.title}
description={config.description}
icon={logo}

View file

@ -37,7 +37,9 @@ export const HeaderMenuPortal: FC<PropsWithChildren<unknown>> = ({ children }) =
return () => {
portalNode.unmount();
setHeaderActionMenu(undefined);
if (setHeaderActionMenu) {
setHeaderActionMenu(undefined);
}
};
}, [portalNode, setHeaderActionMenu, services]);

View file

@ -9,7 +9,6 @@ import {
EuiButton,
EuiButtonEmpty,
EuiCheckbox,
EuiConfirmModal,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
@ -18,12 +17,10 @@ import {
EuiFlyoutHeader,
EuiLoadingSpinner,
EuiSpacer,
EuiTab,
EuiTabs,
EuiTitle,
} from '@elastic/eui';
import type { FC } from 'react';
import { useEffect, useMemo, useCallback } from 'react';
import { useEffect, useMemo } from 'react';
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
@ -47,39 +44,6 @@ const LoadingSpinner: FC = () => (
</>
);
const SwitchTabsConfirm: FC<{ onCancel: () => void; onConfirm: () => void }> = ({
onCancel,
onConfirm,
}) => (
<EuiConfirmModal
title={i18n.translate('xpack.ml.importExport.exportFlyout.switchTabsConfirm.title', {
defaultMessage: 'Change tabs?',
})}
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText={i18n.translate(
'xpack.ml.importExport.exportFlyout.switchTabsConfirm.cancelButton',
{
defaultMessage: 'Cancel',
}
)}
confirmButtonText={i18n.translate(
'xpack.ml.importExport.exportFlyout.switchTabsConfirm.confirmButton',
{
defaultMessage: 'Confirm',
}
)}
defaultFocusedButton="confirm"
>
<p>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.switchTabsConfirm.text"
defaultMessage="Changing tabs will clear currently selected jobs"
/>
</p>
</EuiConfirmModal>
);
export interface ExportJobsFlyoutContentProps {
currentTab: JobType;
isADEnabled: boolean;
@ -119,8 +83,6 @@ export const ExportJobsFlyoutContent = ({
const [loadingDFAJobs, setLoadingDFAJobs] = useState(true);
const [exporting, setExporting] = useState(false);
const [switchTabConfirmVisible, setSwitchTabConfirmVisible] = useState(false);
const [switchTabNextTab, setSwitchTabNextTab] = useState<JobType>(currentTab);
const [selectedJobDependencies, setSelectedJobDependencies] = useState<JobDependencies>([]);
async function onExport() {
@ -164,12 +126,6 @@ export const ExportJobsFlyoutContent = ({
}
}
function switchTab(jobType: JobType) {
setSwitchTabConfirmVisible(false);
setSelectedJobIds([]);
setSelectedJobType(jobType);
}
function onSelectAll() {
const ids = selectedJobType === 'anomaly-detector' ? adJobIds : dfaJobIds;
if (selectedJobIds.length === ids.length) {
@ -179,25 +135,6 @@ export const ExportJobsFlyoutContent = ({
}
}
const attemptTabSwitch = useCallback(
(jobType: JobType) => {
if (jobType === selectedJobType) {
return;
}
// if the user has already selected some jobs, open a confirm modal
// rather than changing tabs
if (selectedJobIds.length > 0) {
setSwitchTabNextTab(jobType);
setSwitchTabConfirmVisible(true);
return;
}
switchTab(jobType);
},
[selectedJobIds, selectedJobType]
);
useEffect(() => {
setSelectedJobDependencies(
jobDependencies.filter(({ jobId }) => selectedJobIds.includes(jobId))
@ -209,7 +146,6 @@ export const ExportJobsFlyoutContent = ({
setLoadingADJobs(true);
setLoadingDFAJobs(true);
setExporting(false);
setSwitchTabConfirmVisible(false);
setAdJobIds([]);
setSelectedJobIds([]);
setSelectedJobType(currentTab);
@ -282,41 +218,27 @@ export const ExportJobsFlyoutContent = ({
<h2>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.flyoutHeader"
defaultMessage="Export jobs"
defaultMessage="Export {selectedMLType} jobs"
values={{
selectedMLType:
currentTab === 'anomaly-detector' ? (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adTab"
defaultMessage="Anomaly detection"
/>
) : (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaTab"
defaultMessage="Analytics"
/>
),
}}
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<ExportJobDependenciesWarningCallout jobs={selectedJobDependencies} />
<EuiTabs size="s">
{isADEnabled === true ? (
<EuiTab
isSelected={selectedJobType === 'anomaly-detector'}
onClick={() => attemptTabSwitch('anomaly-detector')}
disabled={exporting}
data-test-subj="mlJobMgmtExportJobsADTab"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adTab"
defaultMessage="Anomaly detection"
/>
</EuiTab>
) : null}
{isDFAEnabled === true ? (
<EuiTab
isSelected={selectedJobType === 'data-frame-analytics'}
onClick={() => attemptTabSwitch('data-frame-analytics')}
disabled={exporting}
data-test-subj="mlJobMgmtExportJobsDFATab"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaTab"
defaultMessage="Analytics"
/>
</EuiTab>
) : null}
</EuiTabs>
<EuiSpacer size="s" />
<>
{isADEnabled === true && selectedJobType === 'anomaly-detector' && (
@ -432,13 +354,6 @@ export const ExportJobsFlyoutContent = ({
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
{switchTabConfirmVisible === true ? (
<SwitchTabsConfirm
onCancel={setSwitchTabConfirmVisible.bind(null, false)}
onConfirm={() => switchTab(switchTabNextTab)}
/>
) : null}
</>
);
};

View file

@ -43,15 +43,13 @@ export const AnomalyDetectionInfoButton: FC<Props> = ({
prefix: 'adJobInfoContextMenu',
suffix: jobId,
});
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const onButtonClick = () => setPopover((prev) => !prev);
const closePopover = () => {
setPopover(false);
};
const { setActiveFlyout, setActiveJobId } = useJobInfoFlyouts();
const panels = useMemo(
const panels = useMemo<EuiContextMenuPanelDescriptor[]>(
() => {
return [
{
@ -71,7 +69,7 @@ export const AnomalyDetectionInfoButton: FC<Props> = ({
share,
}),
},
] as EuiContextMenuPanelDescriptor[];
];
},
// globalState is an object with references change on every render, so we are stringifying it here
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -10,6 +10,7 @@ import { render } from '@testing-library/react';
import type { IdBadgesProps } from './id_badges';
import { IdBadges } from './id_badges';
import type { MlSummaryJob } from '../../../../../common/types/anomaly_detection_jobs';
import { ML_PAGES } from '../../../../locator';
jest.mock('../../../contexts/kibana', () => ({
useMlKibana: () => ({
@ -21,7 +22,7 @@ jest.mock('../../../contexts/kibana', () => ({
}));
const props: IdBadgesProps = {
page: 'jobs',
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
limit: 2,
selectedGroups: [
{

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { EuiButtonEmpty, EuiFlexItem, EuiFlexGroup, EuiHorizontalRule } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
@ -23,6 +24,8 @@ import type {
import { FeedBackButton } from '../feedback_button';
import { JobInfoFlyoutsProvider } from '../../jobs/components/job_details_flyout';
import { JobInfoFlyoutsManager } from '../../jobs/components/job_details_flyout/job_details_context_manager';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
import { useCreateAndNavigateToManagementMlLink } from '../../contexts/kibana/use_create_url';
import { useJobSelectionFlyout } from '../../contexts/ml/use_job_selection_flyout';
export interface GroupObj {
@ -142,6 +145,11 @@ export function JobSelector({
setSelectedIds(newSelection);
onSelectionChange?.({ jobIds: newSelection, time: undefined });
};
const [canGetJobs, canCreateJob] = usePermissionCheck(['canGetJobs', 'canCreateJob']);
const redirectToADJobManagement = useCreateAndNavigateToManagementMlLink('', 'anomaly_detection');
function renderJobSelectionBar() {
return (
<>
@ -193,6 +201,30 @@ export function JobSelector({
<EuiFlexItem grow={false}>
<FeedBackButton jobIds={selectedIds} page={page} />
</EuiFlexItem>
{canGetJobs ? (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
color="primary"
onClick={redirectToADJobManagement}
disabled={!canGetJobs}
data-test-subj="mlJobSelectorManageJobsButton"
>
{canCreateJob ? (
<FormattedMessage
id="xpack.ml.jobSelector.manageJobsLinkLabel"
defaultMessage="Manage jobs"
/>
) : (
<FormattedMessage
id="xpack.ml.jobSelector.viewJobsLinkLabel"
defaultMessage="View jobs"
/>
)}
</EuiButtonEmpty>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
<EuiHorizontalRule margin="s" />
</>

View file

@ -0,0 +1,116 @@
/*
* 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 { FC } from 'react';
import React, { useMemo, useState } from 'react';
import type { EuiContextMenuPanelDescriptor } from '@elastic/eui';
import { EuiButton, EuiContextMenu, EuiPopover, useGeneratedHtmlId } from '@elastic/eui';
import { useUrlState } from '@kbn/ml-url-state';
import { ML_PAGES, type MlPages } from '../../../../../common/constants/locator';
import { useJobInfoFlyouts } from '../../../jobs/components/job_details_flyout';
import { useMlKibana } from '../../../contexts/kibana';
import { getOptionsForJobSelectorMenuItems } from '../group_or_job_selector_menu/get_options_for_job_selector_menu';
interface Props {
jobId: string;
page: MlPages;
onRemoveJobId: (jobOrGroupId: string[]) => void;
removeJobIdDisabled: boolean;
isSingleMetricViewerDisabled: boolean;
}
export const AnomalyDetectionInfoButton: FC<Props> = ({
jobId,
page,
onRemoveJobId,
removeJobIdDisabled,
isSingleMetricViewerDisabled,
}) => {
const [isPopoverOpen, setPopover] = useState(false);
const {
services: {
share,
application: { navigateToUrl },
},
} = useMlKibana();
const [globalState] = useUrlState('_g');
const popoverId = useGeneratedHtmlId({
prefix: 'adJobInfoContextMenu',
suffix: jobId,
});
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const { setActiveFlyout, setActiveJobId } = useJobInfoFlyouts();
const panels = useMemo(
() => {
return [
{
id: 0,
items: getOptionsForJobSelectorMenuItems({
jobId,
page,
onRemoveJobId,
removeJobIdDisabled,
showRemoveJobId: page === ML_PAGES.ANOMALY_EXPLORER,
isSingleMetricViewerDisabled,
closePopover,
globalState,
setActiveFlyout,
setActiveJobId,
navigateToUrl,
share,
}),
},
] as EuiContextMenuPanelDescriptor[];
},
// globalState is an object with references change on every render, so we are stringifying it here
// eslint-disable-next-line react-hooks/exhaustive-deps
[
jobId,
page,
setActiveJobId,
setActiveFlyout,
navigateToUrl,
share.url.locators,
removeJobIdDisabled,
onRemoveJobId,
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(globalState),
]
);
const button = (
<EuiButton
data-test-subj="mlJobSelectionBadge"
iconType="boxesVertical"
iconSide="right"
onClick={onButtonClick}
size="s"
color="text"
>
{jobId}
</EuiButton>
);
return (
<EuiPopover
id={popoverId}
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
);
};

View file

@ -0,0 +1,47 @@
/*
* 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, useState } from 'react';
import { Subscription } from 'rxjs';
import { map, distinctUntilChanged } from 'rxjs';
import { DatePickerWrapper } from '@kbn/ml-date-picker';
import { useMlKibana } from '../../contexts/kibana';
export const DatePicker = () => {
const {
services: {
mlServices: { httpService },
},
} = useMlKibana();
useEffect(() => {
const subscriptions = new Subscription();
subscriptions.add(
httpService.getLoadingCount$
.pipe(
map((v) => v !== 0),
distinctUntilChanged()
)
.subscribe((loading) => {
setIsLoading(loading);
})
);
return function cleanup() {
subscriptions.unsubscribe();
};
}, [httpService?.getLoadingCount$]);
const [isLoading, setIsLoading] = useState(false);
return (
<DatePickerWrapper
isLoading={isLoading}
width="full"
dataTestSubj="mlDatePickerRefreshPageButton"
/>
);
};

View file

@ -8,10 +8,11 @@
import type { FC } from 'react';
import React, { createContext, useEffect, useMemo, useState } from 'react';
import { createHtmlPortalNode, type HtmlPortalNode } from 'react-reverse-portal';
import { Redirect } from 'react-router-dom';
import { Redirect, useLocation } from 'react-router-dom';
import { Routes, Route } from '@kbn/shared-ux-router';
import { Subscription } from 'rxjs';
import { EuiPageSection } from '@elastic/eui';
import type { EuiPaddingSize } from '@elastic/eui';
import { EuiPageSection, EuiPageHeader } from '@elastic/eui';
import { map, distinctUntilChanged } from 'rxjs';
import { i18n } from '@kbn/i18n';
@ -19,10 +20,16 @@ import { type AppMountParameters } from '@kbn/core/public';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { DatePickerWrapper } from '@kbn/ml-date-picker';
import { DEPRECATED_ML_ROUTE_TO_NEW_ROUTE } from '../../../../common/constants/locator';
import * as routes from '../../routing/routes';
import * as overviewRoutes from '../../routing/routes/overview_management';
import * as anomalyDetectionRoutes from '../../routing/routes/anomaly_detection_management';
import * as dfaRoutes from '../../routing/routes/data_frame_analytics_management';
import * as settingsRoutes from '../../routing/routes/settings';
import * as trainedModelsRoutes from '../../routing/routes/trained_models';
import { MlPageWrapper } from '../../routing/ml_page_wrapper';
import { useMlKibana, useNavigateToPath } from '../../contexts/kibana';
import { useMlKibana, useMlManagementLocator, useNavigateToPath } from '../../contexts/kibana';
import type { NavigateToPath } from '../../contexts/kibana';
import type { MlRoute, PageDependencies } from '../../routing/router';
import { useActiveRoute } from '../../routing/use_active_route';
import { useDocTitle } from '../../routing/use_doc_title';
@ -31,6 +38,15 @@ import { MlPageHeaderRenderer } from '../page_header/page_header';
import { useSideNavItems } from './side_nav';
import { useEnabledFeatures } from '../../contexts/ml';
import { MANAGEMENT_SECTION_IDS } from '../../management';
import type { NavigateToApp } from '../../routing/breadcrumbs';
interface RouteToPath {
[key: string]: (navigateToPath: NavigateToPath, basePath: string) => MlRoute;
}
interface RouteToApp {
[key: string]: (navigateToApp: NavigateToApp, basePath: string) => MlRoute;
}
type RouteModules = RouteToPath | RouteToApp;
const ML_APP_SELECTOR = '[data-test-subj="mlApp"]';
@ -50,156 +66,238 @@ export const MlPageControlsContext = createContext<{
* Main page component of the ML App
* @constructor
*/
export const MlPage: FC<{ pageDeps: PageDependencies }> = React.memo(({ pageDeps }) => {
const navigateToPath = useNavigateToPath();
const {
services: {
http: { basePath },
mlServices: { httpService },
},
} = useMlKibana();
const { showMLNavMenu } = useEnabledFeatures();
export const MlPage: FC<{ pageDeps: PageDependencies; entryPoint?: string }> = React.memo(
({ pageDeps, entryPoint }) => {
const navigateToPath = useNavigateToPath();
const { pathname, search } = useLocation();
const mlManagementLocator = useMlManagementLocator();
const headerPortalNode = useMemo(() => createHtmlPortalNode(), []);
const [isHeaderMounted, setIsHeaderMounted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const subscriptions = new Subscription();
subscriptions.add(
httpService.getLoadingCount$
.pipe(
map((v) => v !== 0),
distinctUntilChanged()
)
.subscribe((loading) => {
setIsLoading(loading);
})
);
return function cleanup() {
subscriptions.unsubscribe();
};
}, [httpService?.getLoadingCount$]);
const routeList = useMemo(
() =>
Object.values(routes)
.map((routeFactory) => routeFactory(navigateToPath, basePath.get()))
.filter((d) => !d.disabled),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const activeRoute = useActiveRoute(routeList);
const rightSideItems = useMemo(() => {
return [
...(activeRoute.enableDatePicker
? [<DatePickerWrapper isLoading={isLoading} width="full" />]
: []),
];
}, [activeRoute.enableDatePicker, isLoading]);
useDocTitle(activeRoute);
// The deprecated `KibanaPageTemplate` from`'@kbn/kibana-react-plugin/public'`
// had a `pageBodyProps` prop where we could pass in the `data-test-subj` for
// the `main` element. This is no longer available in the update template
// imported from `'@kbn/shared-ux-page-kibana-template'`. The following is a
// workaround to add the `data-test-subj` on the `main` element again.
useEffect(() => {
const mlApp = document.querySelector(ML_APP_SELECTOR) as HTMLElement;
if (mlApp && typeof activeRoute?.['data-test-subj'] === 'string') {
const mlAppMain = mlApp.querySelector('main') as HTMLElement;
if (mlAppMain) {
mlAppMain.setAttribute('data-test-subj', activeRoute?.['data-test-subj']);
}
}
}, [activeRoute]);
const sideNavItems = useSideNavItems(activeRoute);
return (
<MlPageControlsContext.Provider
value={{
setHeaderActionMenu: pageDeps.setHeaderActionMenu,
headerPortal: headerPortalNode,
setIsHeaderMounted,
isHeaderMounted,
}}
>
<KibanaPageTemplate
className={'ml-app'}
data-test-subj={'mlApp'}
restrictWidth={false}
panelled
solutionNav={
showMLNavMenu
? {
name: i18n.translate('xpack.ml.plugin.title', {
defaultMessage: 'Machine Learning',
}),
icon: 'machineLearningApp',
items: sideNavItems,
}
: undefined
useEffect(
// Auto-redirect bookmarked jobs list and data frame analytics list
// to the new management pages, and keep the search string as much
function autoRedirectToManagementPages() {
if (mlManagementLocator) {
const searchString = decodeURIComponent(search);
let decodedSearch = search;
const oldPath = pathname.slice(1);
const newPath =
DEPRECATED_ML_ROUTE_TO_NEW_ROUTE[
oldPath as keyof typeof DEPRECATED_ML_ROUTE_TO_NEW_ROUTE
];
if (oldPath && newPath) {
if (oldPath !== newPath) {
decodedSearch = searchString.replace(`=(${oldPath}:`, `=('':`);
}
mlManagementLocator.navigate({
sectionId: 'ml',
appId: `${newPath}${decodedSearch}`,
});
return;
}
}
pageHeader={{
pageTitle: <MlPageHeaderRenderer />,
rightSideItems,
restrictWidth: false,
},
[pathname, navigateToPath, mlManagementLocator, search]
);
const {
services: {
application: { navigateToApp },
http: { basePath },
mlServices: { httpService },
},
} = useMlKibana();
const { showMLNavMenu } = useEnabledFeatures();
const headerPortalNode = useMemo(() => createHtmlPortalNode(), []);
const [isHeaderMounted, setIsHeaderMounted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const subscriptions = new Subscription();
subscriptions.add(
httpService.getLoadingCount$
.pipe(
map((v) => v !== 0),
distinctUntilChanged()
)
.subscribe((loading) => {
setIsLoading(loading);
})
);
return function cleanup() {
subscriptions.unsubscribe();
};
}, [httpService?.getLoadingCount$]);
const routeList = useMemo(
() => {
let currentRoutes: RouteModules = routes as RouteToPath;
switch (entryPoint) {
case MANAGEMENT_SECTION_IDS.OVERVIEW:
currentRoutes = overviewRoutes as RouteToApp;
break;
case MANAGEMENT_SECTION_IDS.ANOMALY_DETECTION:
currentRoutes = anomalyDetectionRoutes as RouteToApp;
break;
case MANAGEMENT_SECTION_IDS.ANALYTICS:
currentRoutes = dfaRoutes as RouteToApp;
break;
case MANAGEMENT_SECTION_IDS.TRAINED_MODELS:
currentRoutes = trainedModelsRoutes as RouteToApp;
break;
case MANAGEMENT_SECTION_IDS.AD_SETTINGS:
currentRoutes = settingsRoutes as RouteToApp;
break;
default:
break;
}
const currentRoutesList = Object.values(currentRoutes);
if (entryPoint !== undefined) {
return currentRoutesList
.map((routeFactory) => routeFactory(navigateToApp))
.filter((d) => !d.disabled);
} else {
return currentRoutesList
.map((routeFactory) => routeFactory(navigateToPath, basePath.get()))
.filter((d) => !d.disabled);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[entryPoint]
);
const activeRoute = useActiveRoute(routeList);
const rightSideItems = useMemo(() => {
return [
...(activeRoute.enableDatePicker
? [<DatePickerWrapper isLoading={isLoading} width="full" />]
: []),
];
}, [activeRoute.enableDatePicker, isLoading]);
useDocTitle(activeRoute);
// The deprecated `KibanaPageTemplate` from`'@kbn/kibana-react-plugin/public'`
// had a `pageBodyProps` prop where we could pass in the `data-test-subj` for
// the `main` element. This is no longer available in the update template
// imported from `'@kbn/shared-ux-page-kibana-template'`. The following is a
// workaround to add the `data-test-subj` on the `main` element again.
useEffect(() => {
const mlApp = document.querySelector(ML_APP_SELECTOR) as HTMLElement;
if (mlApp && typeof activeRoute?.['data-test-subj'] === 'string') {
const mlAppMain = mlApp.querySelector('main') as HTMLElement;
if (mlAppMain) {
mlAppMain.setAttribute('data-test-subj', activeRoute?.['data-test-subj']);
}
}
}, [activeRoute]);
const sideNavItems = useSideNavItems(activeRoute);
return (
<MlPageControlsContext.Provider
value={{
setHeaderActionMenu: pageDeps.setHeaderActionMenu,
headerPortal: headerPortalNode,
setIsHeaderMounted,
isHeaderMounted,
}}
>
<CommonPageWrapper
headerPortal={headerPortalNode}
setIsHeaderMounted={setIsHeaderMounted}
pageDeps={pageDeps}
routeList={routeList}
/>
</KibanaPageTemplate>
</MlPageControlsContext.Provider>
);
});
{entryPoint === undefined ? (
<KibanaPageTemplate
className={'ml-app'}
data-test-subj={'mlApp'}
restrictWidth={false}
panelled
solutionNav={
showMLNavMenu
? {
name: i18n.translate('xpack.ml.plugin.title', {
defaultMessage: 'Machine Learning',
}),
icon: 'machineLearningApp',
items: sideNavItems,
}
: undefined
}
pageHeader={{
pageTitle: <MlPageHeaderRenderer />,
rightSideItems,
restrictWidth: false,
}}
>
<CommonPageWrapper
headerPortal={headerPortalNode}
setIsHeaderMounted={setIsHeaderMounted}
pageDeps={pageDeps}
routeList={routeList}
/>
</KibanaPageTemplate>
) : (
<>
<EuiPageHeader pageTitle={<MlPageHeaderRenderer />} rightSideItems={rightSideItems} />
<CommonPageWrapper
headerPortal={headerPortalNode}
setIsHeaderMounted={setIsHeaderMounted}
pageDeps={pageDeps}
routeList={routeList}
paddingSize="none"
/>
</>
)}
</MlPageControlsContext.Provider>
);
}
);
interface CommonPageWrapperProps {
setIsHeaderMounted: (v: boolean) => void;
pageDeps: PageDependencies;
routeList: MlRoute[];
headerPortal: HtmlPortalNode;
paddingSize?: EuiPaddingSize;
}
const CommonPageWrapper: FC<CommonPageWrapperProps> = React.memo(({ pageDeps, routeList }) => {
const {
services: { application },
} = useMlKibana();
const CommonPageWrapper: FC<CommonPageWrapperProps> = React.memo(
({ pageDeps, routeList, paddingSize }) => {
const {
services: { application },
} = useMlKibana();
return (
/** RedirectAppLinks intercepts all <a> tags to use navigateToUrl
* avoiding full page reload **/
<RedirectAppLinks coreStart={{ application }}>
<EuiPageSection restrictWidth={false}>
<Routes>
{routeList.map((route) => {
return (
<Route
key={route.path}
path={route.path}
exact
render={(props) => {
window.setTimeout(() => {
pageDeps.setBreadcrumbs(route.breadcrumbs);
});
return (
<MlPageWrapper path={route.path}>{route.render(props, pageDeps)}</MlPageWrapper>
);
}}
/>
);
})}
<Redirect to="/overview" />
</Routes>
</EuiPageSection>
</RedirectAppLinks>
);
});
return (
/** RedirectAppLinks intercepts all <a> tags to use navigateToUrl
* avoiding full page reload **/
<RedirectAppLinks coreStart={{ application }}>
<EuiPageSection restrictWidth={false} paddingSize={paddingSize}>
<Routes>
{routeList.map((route) => {
return (
<Route
key={route.path}
path={route.path}
exact
render={(props) => {
window.setTimeout(() => {
pageDeps.setBreadcrumbs(route.breadcrumbs);
});
return (
<MlPageWrapper path={route.path}>
{route.render(props, pageDeps)}
</MlPageWrapper>
);
}}
/>
);
})}
<Redirect to="/overview" />
</Routes>
</EuiPageSection>
</RedirectAppLinks>
);
}
);

View file

@ -8,16 +8,15 @@
import { i18n } from '@kbn/i18n';
import type { EuiSideNavItemType } from '@elastic/eui';
import type { ReactNode } from 'react';
import React, { useCallback, useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { CHANGE_POINT_DETECTION_ENABLED } from '@kbn/aiops-change-point-detection/constants';
import { useUrlState } from '@kbn/ml-url-state';
import { NotificationsIndicator } from './notifications_indicator';
import type { MlLocatorParams } from '../../../../common/types/locator';
import { useMlLocator, useNavigateToPath } from '../../contexts/kibana';
import { isFullLicense } from '../../license';
import type { MlRoute } from '../../routing';
import { ML_PAGES } from '../../../../common/constants/locator';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
import { useEnabledFeatures } from '../../contexts/ml';
export interface Tab {
id: string;
@ -38,7 +37,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
const navigateToPath = useNavigateToPath();
const mlFeaturesDisabled = !isFullLicense();
const canViewMlNodes = usePermissionCheck('canViewMlNodes');
const { isADEnabled, isDFAEnabled } = useEnabledFeatures();
const [globalState] = useUrlState('_g');
@ -85,192 +84,78 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
testSubj: 'mlMainTab overview',
},
{
id: 'notifications',
pathId: ML_PAGES.NOTIFICATIONS,
name: disableLinks ? (
i18n.translate('xpack.ml.navMenu.notificationsTabLinkText', {
defaultMessage: 'Notifications',
})
) : (
<NotificationsIndicator />
),
disabled: disableLinks,
testSubj: 'mlMainTab notifications',
},
{
id: 'memory_usage',
pathId: ML_PAGES.MEMORY_USAGE,
name: i18n.translate('xpack.ml.navMenu.memoryUsageText', {
defaultMessage: 'Memory Usage',
}),
disabled: disableLinks || !canViewMlNodes,
testSubj: 'mlMainTab nodesOverview',
},
],
},
{
id: 'anomaly_detection_section',
name: i18n.translate('xpack.ml.navMenu.anomalyDetectionTabLinkText', {
defaultMessage: 'Anomaly Detection',
}),
disabled: disableLinks,
items: [
{
id: 'anomaly_detection',
name: i18n.translate('xpack.ml.navMenu.anomalyDetection.jobsManagementText', {
defaultMessage: 'Jobs',
}),
disabled: disableLinks,
pathId: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
testSubj: 'mlMainTab anomalyDetection',
},
{
id: 'anomaly_explorer',
name: i18n.translate('xpack.ml.navMenu.anomalyDetection.anomalyExplorerText', {
defaultMessage: 'Anomaly Explorer',
}),
disabled: disableLinks,
pathId: ML_PAGES.ANOMALY_EXPLORER,
testSubj: 'mlMainTab anomalyExplorer',
},
{
id: 'single_metric_viewer',
name: i18n.translate('xpack.ml.navMenu.anomalyDetection.singleMetricViewerText', {
defaultMessage: 'Single Metric Viewer',
}),
pathId: ML_PAGES.SINGLE_METRIC_VIEWER,
disabled: disableLinks,
testSubj: 'mlMainTab singleMetricViewer',
},
{
id: 'settings',
pathId: ML_PAGES.SETTINGS,
name: i18n.translate('xpack.ml.navMenu.settingsTabLinkText', {
defaultMessage: 'Settings',
}),
disabled: disableLinks,
testSubj: 'mlMainTab settings',
highlightNestedRoutes: true,
},
{
id: 'supplied_cofigurations',
name: i18n.translate(
'xpack.ml.navMenu.anomalyDetection.suppliedConfigurationsLinkText',
{
defaultMessage: 'Supplied Configurations',
}
),
disabled: disableLinks,
pathId: ML_PAGES.SUPPLIED_CONFIGURATIONS,
testSubj: 'mlMainTab suppliedConfigurations',
},
],
},
{
id: 'data_frame_analytics_section',
name: i18n.translate('xpack.ml.navMenu.dataFrameAnalyticsTabLinkText', {
defaultMessage: 'Data Frame Analytics',
}),
disabled: disableLinks,
items: [
{
id: 'data_frame_analytics_jobs',
pathId: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
name: i18n.translate('xpack.ml.navMenu.dataFrameAnalytics.jobsManagementText', {
defaultMessage: 'Jobs',
}),
disabled: disableLinks,
testSubj: 'mlMainTab dataFrameAnalytics',
},
{
id: 'data_frame_analytics_results_explorer',
pathId: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
name: i18n.translate('xpack.ml.navMenu.dataFrameAnalytics.resultsExplorerText', {
defaultMessage: 'Results Explorer',
}),
disabled: disableLinks,
testSubj: 'mlMainTab dataFrameAnalyticsResultsExplorer',
},
{
id: 'data_frame_analytics_job_map',
pathId: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
name: i18n.translate('xpack.ml.navMenu.dataFrameAnalytics.analyticsMapText', {
defaultMessage: 'Analytics Map',
}),
disabled: disableLinks,
testSubj: 'mlMainTab dataFrameAnalyticsMap',
},
],
},
{
id: 'model_management',
name: i18n.translate('xpack.ml.navMenu.modelManagementText', {
defaultMessage: 'Model Management',
}),
disabled: disableLinks,
items: [
{
id: 'trained_models',
pathId: ML_PAGES.TRAINED_MODELS_MANAGE,
name: i18n.translate('xpack.ml.navMenu.trainedModelsText', {
defaultMessage: 'Trained Models',
}),
disabled: disableLinks,
testSubj: 'mlMainTab trainedModels',
},
],
},
{
id: 'datavisualizer',
name: i18n.translate('xpack.ml.navMenu.dataVisualizerTabLinkText', {
defaultMessage: 'Data Visualizer',
}),
disabled: false,
pathId: ML_PAGES.DATA_VISUALIZER,
testSubj: 'mlMainTab dataVisualizer',
items: [
{
id: 'filedatavisualizer',
pathId: ML_PAGES.DATA_VISUALIZER_FILE,
name: i18n.translate('xpack.ml.navMenu.fileDataVisualizerLinkText', {
defaultMessage: 'File',
id: 'datavisualizer',
name: i18n.translate('xpack.ml.navMenu.dataVisualizerTabLinkText', {
defaultMessage: 'Data Visualizer',
}),
disabled: false,
testSubj: 'mlMainTab fileDataVisualizer',
},
{
id: 'data_view_datavisualizer',
pathId: ML_PAGES.DATA_VISUALIZER_INDEX_SELECT,
name: i18n.translate('xpack.ml.navMenu.dataViewDataVisualizerLinkText', {
defaultMessage: 'Data View',
}),
disabled: false,
testSubj: 'mlMainTab indexDataVisualizer',
relatedRouteIds: ['data_view_datavisualizer'],
},
{
id: 'esql_datavisualizer',
pathId: ML_PAGES.DATA_VISUALIZER_ESQL,
name: i18n.translate('xpack.ml.navMenu.esqlDataVisualizerLinkText', {
defaultMessage: 'ES|QL',
}),
disabled: false,
testSubj: 'mlMainTab esqlDataVisualizer',
relatedRouteIds: ['data_view_datavisualizer_esql'],
},
{
id: 'data_drift',
pathId: ML_PAGES.DATA_DRIFT_INDEX_SELECT,
name: i18n.translate('xpack.ml.navMenu.dataComparisonText', {
defaultMessage: 'Data Drift',
}),
disabled: disableLinks,
testSubj: 'mlMainTab dataDrift',
relatedRouteIds: ['data_drift'],
pathId: ML_PAGES.DATA_VISUALIZER,
testSubj: 'mlMainTab dataVisualizer',
},
],
},
...(isADEnabled
? [
{
id: 'anomaly_detection_section',
name: i18n.translate('xpack.ml.navMenu.anomalyDetectionTabLinkText', {
defaultMessage: 'Anomaly Detection',
}),
disabled: disableLinks || !isADEnabled,
items: [
{
id: 'anomaly_explorer',
name: i18n.translate('xpack.ml.navMenu.anomalyDetection.anomalyExplorerText', {
defaultMessage: 'Anomaly Explorer',
}),
disabled: disableLinks || !isADEnabled,
pathId: ML_PAGES.ANOMALY_EXPLORER,
testSubj: 'mlMainTab anomalyExplorer',
},
{
id: 'single_metric_viewer',
name: i18n.translate('xpack.ml.navMenu.anomalyDetection.singleMetricViewerText', {
defaultMessage: 'Single Metric Viewer',
}),
pathId: ML_PAGES.SINGLE_METRIC_VIEWER,
disabled: disableLinks || !isADEnabled,
testSubj: 'mlMainTab singleMetricViewer',
},
],
},
]
: []),
...(isDFAEnabled
? [
{
id: 'data_frame_analytics_section',
name: i18n.translate('xpack.ml.navMenu.dataFrameAnalyticsTabLinkText', {
defaultMessage: 'Data Frame Analytics',
}),
disabled: disableLinks || !isDFAEnabled,
items: [
{
id: 'data_frame_analytics_results_explorer',
pathId: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
name: i18n.translate('xpack.ml.navMenu.dataFrameAnalytics.resultsExplorerText', {
defaultMessage: 'Results Explorer',
}),
disabled: disableLinks || !isDFAEnabled,
testSubj: 'mlMainTab dataFrameAnalyticsResultsExplorer',
},
{
id: 'data_frame_analytics_job_map',
pathId: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
name: i18n.translate('xpack.ml.navMenu.dataFrameAnalytics.analyticsMapText', {
defaultMessage: 'Analytics Map',
}),
disabled: disableLinks || !isDFAEnabled,
testSubj: 'mlMainTab dataFrameAnalyticsMap',
},
],
},
]
: []),
];
mlTabs.push({
@ -318,7 +203,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
});
return mlTabs;
}, [mlFeaturesDisabled, canViewMlNodes]);
}, [mlFeaturesDisabled, isADEnabled, isDFAEnabled]);
const getTabItem: (tab: Tab) => EuiSideNavItemType<unknown> = useCallback(
(tab: Tab) => {

View file

@ -0,0 +1,31 @@
/*
* 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 { EuiEmptyPromptProps } from '@elastic/eui';
import { EuiEmptyPrompt, EuiImage } from '@elastic/eui';
export const MLEmptyPromptCard = ({
title,
body,
actions,
iconSrc,
iconAlt,
'data-test-subj': dataTestSubj,
}: Omit<EuiEmptyPromptProps, 'title'> & { title: string; iconSrc: string; iconAlt: string }) => (
<EuiEmptyPrompt
layout="horizontal"
hasBorder={true}
hasShadow={false}
icon={<EuiImage size="fullWidth" src={iconSrc} alt={iconAlt} />}
title={<h3>{title}</h3>}
titleSize="s"
body={body}
actions={actions}
data-test-subj={dataTestSubj}
/>
);

View file

@ -19,17 +19,14 @@ import { checkPermission } from '../../capabilities/check_capabilities';
import { getScopeFieldDefaults } from './utils';
import { FormattedMessage } from '@kbn/i18n-react';
import { ML_PAGES } from '../../../../common/constants/locator';
import { useMlLocator, useNavigateToPath } from '../../contexts/kibana';
import { MANAGEMENT_SECTION_IDS } from '../../management';
import { useCreateAndNavigateToManagementMlLink } from '../../contexts/kibana';
function NoFilterListsCallOut() {
const mlLocator = useMlLocator();
const navigateToPath = useNavigateToPath();
const redirectToFilterManagementPage = async () => {
const path = await mlLocator.getUrl({
page: ML_PAGES.FILTER_LISTS_MANAGE,
});
await navigateToPath(path, true);
};
const redirectToFilterManagementPage = useCreateAndNavigateToManagementMlLink(
ML_PAGES.FILTER_LISTS_MANAGE,
MANAGEMENT_SECTION_IDS.AD_SETTINGS
);
return (
<EuiCallOut

View file

@ -11,6 +11,8 @@ import { BehaviorSubject } from 'rxjs';
import { mlApiServicesMock } from '../../../services/__mocks__/ml_api_services';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { LIGHT_THEME } from '@elastic/charts';
import type { MlCapabilities } from '../../../../../common/types/capabilities';
import { getDefaultCapabilities } from '../../../../../common/types/capabilities';
export const chartsServiceMock = {
theme: {
@ -25,10 +27,16 @@ export const chartsServiceMock = {
},
};
const defaultCapabilities = Object.keys(getDefaultCapabilities()).reduce((acc, key) => {
acc[key] = true;
return acc;
}, {} as Record<string, boolean>);
export const kibanaContextMock = {
services: {
docLinks: { links: { ml: { guide: '' } } },
uiSettings: { get: jest.fn() },
chrome: { recentlyAccessed: { add: jest.fn() } },
chrome: { recentlyAccessed: { add: jest.fn() }, setHelpExtension: jest.fn() },
application: {
navigateToApp: jest.fn(),
navigateToUrl: jest.fn(),
@ -63,6 +71,8 @@ export const kibanaContextMock = {
mlServices: {
mlApi: mlApiServicesMock,
mlCapabilities: {
capabilities$: new BehaviorSubject(defaultCapabilities),
getCapabilities: jest.fn().mockResolvedValue(defaultCapabilities),
refreshCapabilities: jest.fn(),
},
mlFieldFormatService: {
@ -76,3 +86,20 @@ export const kibanaContextMock = {
export const useMlKibana = jest.fn(() => {
return kibanaContextMock;
});
export const getMockedContextWithCapabilities = (capabilities: Partial<MlCapabilities>) => {
return {
...kibanaContextMock,
services: {
...kibanaContextMock.services,
mlServices: {
...kibanaContextMock.services.mlServices,
mlCapabilities: {
...kibanaContextMock.services.mlServices.mlCapabilities,
getCapabilities: jest.fn().mockResolvedValue(capabilities),
capabilities$: new BehaviorSubject(capabilities),
},
},
},
};
};

View file

@ -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.
*/
export const useNotifications = () => {
return {
toasts: { addSuccess: jest.fn(), addDanger: jest.fn(), addError: jest.fn() },
};
};

View file

@ -11,7 +11,12 @@ export type { NavigateToPath } from './use_navigate_to_path';
export { useNavigateToPath } from './use_navigate_to_path';
export { useUiSettings } from './use_ui_settings_context';
export { useNotifications } from './use_notifications_context';
export { useMlLocator, useMlLink } from './use_create_url';
export {
useMlLocator,
useMlLink,
useMlManagementLocator,
useMlManagementLocatorInternal,
} from './use_create_url';
export { useMlApi } from './use_ml_api_context';
export { useFieldFormatter } from './use_field_formatter';
export { useMlLicenseInfo } from './use_ml_license';

View file

@ -8,9 +8,28 @@
import { useCallback, useEffect, useState } from 'react';
import type { LocatorGetUrlParams } from '@kbn/share-plugin/common/url_service';
import { useUrlState } from '@kbn/ml-url-state';
import { MANAGEMENT_APP_LOCATOR } from '@kbn/deeplinks-management/constants';
import { useMlKibana } from './kibana_context';
import { ML_APP_LOCATOR } from '../../../../common/constants/locator';
import type { MlLocatorParams } from '../../../../common/types/locator';
import { MlManagementLocatorInternal } from '../../../locator/ml_management_locator';
import type { NavigateToMlManagementLink } from '../../jobs/new_job/common/job_creator/util/general';
export const useMlManagementLocator = () => {
const {
services: { share },
} = useMlKibana();
return share.url.locators.get(MANAGEMENT_APP_LOCATOR);
};
export const useMlManagementLocatorInternal = () => {
const {
services: { share },
} = useMlKibana();
return new MlManagementLocatorInternal(share);
};
export const useMlLocator = () => {
const {
@ -24,26 +43,63 @@ export const useMlLink = (params: MlLocatorParams, getUrlParams?: LocatorGetUrlP
const [href, setHref] = useState<string>(params.page);
const mlLocator = useMlLocator();
useEffect(() => {
let isCancelled = false;
const generateUrl = async (_params: MlLocatorParams) => {
if (mlLocator) {
const url = await mlLocator.getUrl(_params, getUrlParams);
if (!isCancelled) {
setHref(url);
useEffect(
function generateMlLink() {
let isCancelled = false;
const generateUrl = async (_params: MlLocatorParams) => {
if (mlLocator) {
const url = await mlLocator.getUrl(_params, getUrlParams);
if (!isCancelled) {
setHref(url);
}
}
}
};
generateUrl(params);
return () => {
isCancelled = true;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params, getUrlParams]);
};
generateUrl(params);
return () => {
isCancelled = true;
};
},
[params, getUrlParams, mlLocator]
);
return href;
};
export const useNavigateToManagementMlLink = (appId: string) => {
const mlManagementLocatorInternal = useMlManagementLocatorInternal();
const [globalState] = useUrlState('_g');
const redirectToMlPage: NavigateToMlManagementLink = useCallback(
async (_page, pageState?) => {
if (mlManagementLocatorInternal) {
const modifiedPageState: MlLocatorParams['pageState'] = pageState ?? {};
if (globalState?.refreshInterval !== undefined) {
// @ts-expect-error globalState override
modifiedPageState.globalState = {
// @ts-expect-error globalState override
...(modifiedPageState.globalState ?? {}),
refreshInterval: globalState.refreshInterval,
};
}
const { path } = await mlManagementLocatorInternal.getUrl(
// @ts-expect-error globalState modification
{ page: _page, pageState: modifiedPageState },
appId
);
await mlManagementLocatorInternal.navigate(path, appId);
} else {
// eslint-disable-next-line no-console
console.error('mlManagementLocatorInternal is not defined');
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[MlManagementLocatorInternal, appId]
);
return redirectToMlPage;
};
export const useCreateAndNavigateToMlLink = (
page: MlLocatorParams['page']
): (() => Promise<void>) => {
@ -58,19 +114,23 @@ export const useCreateAndNavigateToMlLink = (
const redirectToMlPage = useCallback(
async (_page: MlLocatorParams['page']) => {
const pageState =
globalState?.refreshInterval !== undefined
? {
globalState: {
refreshInterval: globalState.refreshInterval,
},
}
: undefined;
if (mlLocator) {
const pageState =
globalState?.refreshInterval !== undefined
? {
globalState: {
refreshInterval: globalState.refreshInterval,
},
}
: undefined;
// TODO: fix ts only interpreting it as MlUrlGenericState if pageState is passed
// @ts-ignore
const url = await mlLocator.getUrl({ page: _page, pageState });
await navigateToUrl(url);
const url = await mlLocator.getUrl({ page: _page, pageState });
await navigateToUrl(url);
} else {
// eslint-disable-next-line no-console
console.error('mlLocator is not defined');
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[mlLocator, navigateToUrl]
@ -79,3 +139,105 @@ export const useCreateAndNavigateToMlLink = (
// returns the onClick callback
return useCallback(() => redirectToMlPage(page), [redirectToMlPage, page]);
};
export const useCreateAndNavigateToManagementMlLink = (
page: MlLocatorParams['page'],
appId: string,
pageState?: MlLocatorParams['pageState']
): (() => Promise<void>) => {
const mlManagementLocatorInternal = useMlManagementLocatorInternal();
const [globalState] = useUrlState('_g');
const {
services: {
application: { navigateToUrl },
},
} = useMlKibana();
const redirectToMlPage = useCallback(
async (_page: MlLocatorParams['page']) => {
if (mlManagementLocatorInternal) {
const modifiedPageState: MlLocatorParams['pageState'] = pageState ?? {};
if (globalState?.refreshInterval !== undefined) {
// @ts-expect-error globalState override
modifiedPageState.globalState = {
// @ts-expect-error globalState override
...(modifiedPageState.globalState ?? {}),
refreshInterval: globalState.refreshInterval,
};
}
const { path } = await mlManagementLocatorInternal.getUrl(
// @ts-expect-error globalState modification
{ page: _page, pageState: modifiedPageState },
appId
);
await mlManagementLocatorInternal.navigate(path, appId);
} else {
// eslint-disable-next-line no-console
console.error('mlManagementLocatorInternal is not defined');
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[MlManagementLocatorInternal, navigateToUrl, JSON.stringify(pageState)]
);
// returns the onClick callback
return useCallback(() => redirectToMlPage(page), [redirectToMlPage, page]);
};
export const useMlManagementLink = (
page: MlLocatorParams['page'],
appId: string,
pageState?: MlLocatorParams['pageState']
): string => {
const [href, setHref] = useState<string>('');
const mlManagementLocatorInternal = useMlManagementLocatorInternal();
const [globalState] = useUrlState('_g');
useEffect(
function generateMlManagementLink() {
let isCancelled = false;
const generateUrl = async (_page: MlLocatorParams['page']) => {
if (mlManagementLocatorInternal) {
const modifiedPageState: MlLocatorParams['pageState'] = pageState ?? {};
if (globalState?.refreshInterval !== undefined) {
// @ts-expect-error globalState override
modifiedPageState.globalState = {
// @ts-expect-error globalState override
...(modifiedPageState.globalState ?? {}),
refreshInterval: globalState.refreshInterval,
};
}
const { url } = await mlManagementLocatorInternal.getUrl(
// @ts-expect-error globalState modification
{ page: _page, pageState: modifiedPageState },
appId
);
if (!isCancelled && url) {
setHref(url);
}
} else {
// eslint-disable-next-line no-console
console.error('mlManagementLocatorInternal is not defined');
}
};
generateUrl(page);
return () => {
isCancelled = true;
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
mlManagementLocatorInternal,
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(pageState),
globalState?.refreshInterval,
appId,
page,
]
);
return href;
};

View file

@ -7,7 +7,7 @@
import { useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import { ML_APP_ROUTE, PLUGIN_ID } from '../../../../common/constants/app';
import { ML_APP_ROUTE, ML_MANAGEMENT_APP_ROUTE, PLUGIN_ID } from '../../../../common/constants/app';
import { useMlKibana } from './kibana_context';
@ -29,11 +29,12 @@ export const useNavigateToPath = () => {
/**
* Handle urls generated by MlUrlGenerator where '/app/ml' is automatically prepended
*/
const url = modifiedPath.includes(ML_APP_ROUTE)
? modifiedPath
: getUrlForApp(PLUGIN_ID, {
path: modifiedPath,
});
const url =
modifiedPath.includes(ML_APP_ROUTE) || modifiedPath.includes(ML_MANAGEMENT_APP_ROUTE)
? modifiedPath
: getUrlForApp(PLUGIN_ID, {
path: modifiedPath,
});
await navigateToUrl(url);
},
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -9,13 +9,14 @@ import type { FC } from 'react';
import React, { Fragment } from 'react';
import { EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlLink } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import { useCreateAndNavigateToManagementMlLink } from '../../../../../contexts/kibana/use_create_url';
export const BackToListPanel: FC = () => {
const analyticsManagementPageLink = useMlLink({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
});
const redirectToAnalyticsList = useCreateAndNavigateToManagementMlLink(
ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
'analytics'
);
return (
<Fragment>
@ -31,7 +32,7 @@ export const BackToListPanel: FC = () => {
defaultMessage: 'Return to the analytics management page.',
}
)}
href={analyticsManagementPageLink}
onClick={redirectToAnalyticsList}
data-test-subj="analyticsWizardCardManagement"
/>
</Fragment>

View file

@ -19,11 +19,11 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useUrlState } from '@kbn/ml-url-state';
import { useCreateAndNavigateToManagementMlLink } from '../../../../../contexts/kibana/use_create_url';
import { useJobInfoFlyouts } from '../../../../../jobs/components/job_details_flyout';
import { useGetAnalytics } from '../../../analytics_management/services/analytics_service';
import type { AnalyticStatsBarStats } from '../../../../../components/stats_bar';
import type { DataFrameAnalyticsListRow } from '../../../analytics_management/components/analytics_list/common';
import { useMlLocator, useNavigateToPath } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../locator';
import { ExpandedRow } from '../../../analytics_management/components/analytics_list/expanded_row';
@ -70,19 +70,14 @@ export const AnalyticsDetailFlyout = () => {
[analytics, analyticsId]
);
const locator = useMlLocator()!;
const navigateToPath = useNavigateToPath();
const [globalState] = useUrlState('_g');
const redirectToAnalyticsList = useCallback(async () => {
const path = await locator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
pageState: {
jobId: globalState?.ml?.jobId,
},
});
await navigateToPath(path, false);
}, [locator, navigateToPath, globalState?.ml?.jobId]);
const pageState = useMemo(() => ({ jobId: globalState?.ml?.jobId }), [globalState?.ml?.jobId]);
const redirectToAnalyticsList = useCreateAndNavigateToManagementMlLink(
ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
'analytics',
pageState
);
const flyoutTitleId = `mlAnalyticsDetailsFlyout-${analyticsId}`;
return isDataFrameAnalyticsDetailsFlyoutOpen ? (

View file

@ -111,7 +111,7 @@ export const Page: FC<{
}
if (jobsExist === false) {
return <AnalyticsEmptyPrompt />;
return <AnalyticsEmptyPrompt showDocsLink />;
}
return (
<>

View file

@ -21,7 +21,7 @@ import {
} from '@kbn/ml-data-frame-analytics-utils';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { DeepReadonly } from '../../../../../../../common/types/common';
import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana';
import { useMlKibana, useMlManagementLocator } from '../../../../../contexts/kibana';
import { DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES } from '../../hooks/use_create_analytics_form';
import type { State } from '../../hooks/use_create_analytics_form/state';
import type { DataFrameAnalyticsListRow } from '../analytics_list/common';
@ -417,7 +417,7 @@ export const useNavigateToWizardWithClonedJob = () => {
...startServices
},
} = useMlKibana();
const navigateToPath = useNavigateToPath();
const mlLocator = useMlManagementLocator();
const canCreateDataView =
capabilities.savedObjectsManagement.edit === true || capabilities.indexPatterns.save === true;
@ -479,11 +479,12 @@ export const useNavigateToWizardWithClonedJob = () => {
}
if (sourceIndexId) {
await navigateToPath(
`/data_frame_analytics/new_job?index=${encodeURIComponent(sourceIndexId)}&jobId=${
item.config.id
}`
);
await mlLocator?.navigate({
sectionId: 'ml',
appId: `analytics/data_frame_analytics/new_job?index=${encodeURIComponent(
sourceIndexId
)}&jobId=${item.config.id}`,
});
}
};
};

View file

@ -24,9 +24,6 @@ import {
} from '@kbn/ml-data-frame-analytics-utils';
import type { ListingPageUrlState } from '@kbn/ml-url-state';
import { useRefreshAnalyticsList } from '../../../../common';
import { usePermissionCheck } from '../../../../../capabilities/check_capabilities';
import { useNavigateToPath } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import type { DataFrameAnalyticsListRow, ItemIdToExpandedRowMap } from './common';
import { DataFrameAnalyticsListColumn } from './common';
@ -35,13 +32,13 @@ import { getJobTypeBadge, getTaskStateBadge, useColumns } from './use_columns';
import { ExpandedRow } from './expanded_row';
import type { AnalyticStatsBarStats } from '../../../../../components/stats_bar';
import { StatsBar } from '../../../../../components/stats_bar';
import { CreateAnalyticsButton } from '../create_analytics_button';
import { filterAnalytics } from '../../../../common/search_bar_filters';
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';
import { DatePicker } from '../../../../../components/ml_page/date_picker';
const filters: EuiSearchBarProps['filters'] = [
{
@ -97,8 +94,6 @@ export const DataFrameAnalyticsList: FC<Props> = ({
pageState,
updatePageState,
}) => {
const navigateToPath = useNavigateToPath();
const searchQueryText = pageState.queryText ?? '';
const setSearchQueryText = useCallback(
(value: string) => {
@ -121,13 +116,6 @@ export const DataFrameAnalyticsList: FC<Props> = ({
const refreshObs = useRefresh();
const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics] = usePermissionCheck([
'canCreateDataFrameAnalytics',
'canStartStopDataFrameAnalytics',
]);
const disabled = !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics;
const getAnalytics = useGetAnalytics(
setAnalytics,
setAnalyticsStats,
@ -198,11 +186,6 @@ export const DataFrameAnalyticsList: FC<Props> = ({
updatePageState
);
const navigateToSourceSelection = useCallback(async () => {
await navigateToPath(ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleSearchOnChange: EuiSearchBarProps['onChange'] = (search) => {
if (search.error !== null) {
setSearchError(search.error.message);
@ -237,7 +220,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
return (
<div data-test-subj="mlAnalyticsJobList">
<EuiSpacer size="m" />
<AnalyticsEmptyPrompt />
<AnalyticsEmptyPrompt showDocsLink />
</div>
);
}
@ -261,6 +244,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
return (
<SpaceManagementContextWrapper>
<EuiSpacer size="m" />
<div data-test-subj="mlAnalyticsJobList">
{modals}
<JobsAwaitingNodeWarning jobCount={jobsAwaitingNodeCount} />
@ -269,10 +253,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<CreateAnalyticsButton
isDisabled={disabled}
navigateToSourceSelection={navigateToSourceSelection}
/>
<DatePicker />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

View file

@ -7,6 +7,7 @@
import type { FC } from 'react';
import React from 'react';
import type { EuiButtonProps } from '@elastic/eui';
import { EuiButton, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities';
@ -14,20 +15,21 @@ import { createPermissionFailureMessage } from '../../../../../capabilities/chec
interface Props {
isDisabled: boolean;
navigateToSourceSelection: () => void;
size?: EuiButtonProps['size'];
}
export const CreateAnalyticsButton: FC<Props> = ({ isDisabled, navigateToSourceSelection }) => {
const handleClick = () => {
navigateToSourceSelection();
};
export const CreateAnalyticsButton: FC<Props> = ({
isDisabled,
navigateToSourceSelection,
size = 's',
}) => {
const button = (
<EuiButton
disabled={isDisabled}
fill
onClick={handleClick}
onClick={navigateToSourceSelection}
iconType="plusInCircle"
size="s"
size={size}
data-test-subj="mlAnalyticsButtonCreate"
>
{i18n.translate('xpack.ml.dataframe.analyticsList.createDataFrameAnalyticsButton', {

View file

@ -7,20 +7,34 @@
import type { FC } from 'react';
import React from 'react';
import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui';
import { EuiButton, EuiButtonEmpty, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import dfaImage from './data_frame_analytics_kibana.png';
import { mlNodesAvailable } from '../../../../../ml_nodes_check';
import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana';
import { useMlKibana, useMlManagementLocator } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import { usePermissionCheck } from '../../../../../capabilities/check_capabilities';
import { MLEmptyPromptCard } from '../../../../../components/overview/ml_empty_prompt_card';
export const AnalyticsEmptyPrompt: FC = () => {
export const TrainedAnalysisTitle = () => (
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.ml.dataFrame.analyticsList.emptyPromptTitle"
defaultMessage="Trained analysis of your data"
/>
</h3>
</EuiTitle>
);
export const AnalyticsEmptyPrompt: FC<{ showDocsLink?: boolean }> = ({ showDocsLink = false }) => {
const {
services: { docLinks },
} = useMlKibana();
const mlLocator = useMlManagementLocator();
const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics] = usePermissionCheck([
'canCreateDataFrameAnalytics',
'canStartStopDataFrameAnalytics',
@ -29,61 +43,61 @@ export const AnalyticsEmptyPrompt: FC = () => {
const disabled =
!mlNodesAvailable() || !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics;
const navigateToPath = useNavigateToPath();
const navigateToSourceSelection = async () => {
await navigateToPath(ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION);
if (!mlLocator) return;
await mlLocator.navigate({
sectionId: 'ml',
appId: `analytics/${ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION}`,
});
};
return (
<EuiEmptyPrompt
layout="horizontal"
hasBorder={false}
hasShadow={false}
icon={
<EuiImage
size="fullWidth"
src={dfaImage}
alt={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', {
defaultMessage: 'Analyze your data with data frame analytics',
})}
<MLEmptyPromptCard
iconSrc={dfaImage}
iconAlt={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', {
defaultMessage: 'Trained analysis of your data',
})}
title={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', {
defaultMessage: 'Trained analysis of your data',
})}
body={
<FormattedMessage
id="xpack.ml.overview.analyticsList.emptyPromptText"
defaultMessage="Train outlier detection, regression, or classification machine learning models using data frame analytics."
/>
}
title={
<h2>
<FormattedMessage
id="xpack.ml.dataFrame.analyticsList.emptyPromptTitle"
defaultMessage="Analyze your data with data frame analytics"
/>
</h2>
}
body={
<>
<p>
<FormattedMessage
id="xpack.ml.overview.analyticsList.emptyPromptText"
defaultMessage="Train outlier detection, regression, or classification machine learning models using data frame analytics."
/>
</p>
</>
}
actions={[
<EuiButton
onClick={navigateToSourceSelection}
isDisabled={disabled}
color="primary"
data-test-subj="mlAnalyticsCreateFirstButton"
>
{i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', {
defaultMessage: 'Create data frame analytics job',
})}
</EuiButton>,
<EuiLink href={docLinks.links.ml.dataFrameAnalytics} target="_blank" external>
<FormattedMessage
id="xpack.ml.common.readDocumentationLink"
defaultMessage="Read documentation"
/>
</EuiLink>,
...[
<EuiButton
onClick={navigateToSourceSelection}
isDisabled={disabled}
fill
color="primary"
data-test-subj="mlAnalyticsCreateFirstButton"
>
<FormattedMessage
id="xpack.ml.dataFrame.analyticsList.emptyPromptButtonText"
defaultMessage="Create Data Frame Analytics job"
/>
</EuiButton>,
],
...(showDocsLink
? [
<EuiButtonEmpty
target="_blank"
href={docLinks.links.ml.dataFrameAnalytics}
data-test-subj="mlAnalyticsReadDocumentationButton"
iconType="popout"
iconSide="left"
>
<FormattedMessage
id="xpack.ml.common.readDocumentationLink"
defaultMessage="Read documentation"
/>
</EuiButtonEmpty>,
]
: []),
]}
data-test-subj="mlNoDataFrameAnalyticsFound"
/>

View file

@ -71,6 +71,7 @@ jest.mock('@kbn/saved-objects-finder-plugin/public', () => {
});
const mockNavigateToPath = jest.fn();
const mockLocatorNavigate = jest.fn();
jest.mock('../../../../../contexts/kibana', () => ({
useMlKibana: () => ({
services: {
@ -88,6 +89,9 @@ jest.mock('../../../../../contexts/kibana', () => ({
toasts: { addSuccess: jest.fn(), addDanger: jest.fn(), addError: jest.fn() },
};
},
useMlManagementLocator: () => ({
navigate: mockLocatorNavigate,
}),
}));
jest.mock('../../../../../util/index_utils', () => {
@ -168,9 +172,10 @@ describe('Data Frame Analytics: <SourceSelection />', () => {
expect(
screen.queryByText('Data views using cross-cluster search are not supported.')
).not.toBeInTheDocument();
expect(mockNavigateToPath).toHaveBeenCalledWith(
'/data_frame_analytics/new_job?index=the-plain-index-pattern-id'
);
expect(mockLocatorNavigate).toHaveBeenCalledWith({
appId: 'analytics/data_frame_analytics/new_job?index=the-plain-index-pattern-id',
sectionId: 'ml',
});
expect(mockGetDataViewAndSavedSearchCallback).toHaveBeenCalledTimes(0);
});
});
@ -225,9 +230,10 @@ describe('Data Frame Analytics: <SourceSelection />', () => {
expect(
screen.queryByText('Data views using cross-cluster search are not supported.')
).not.toBeInTheDocument();
expect(mockNavigateToPath).toHaveBeenCalledWith(
'/data_frame_analytics/new_job?savedSearchId=the-plain-saved-search-id'
);
expect(mockLocatorNavigate).toHaveBeenCalledWith({
appId: 'analytics/data_frame_analytics/new_job?index=the-plain-index-pattern-id',
sectionId: 'ml',
});
});
});
});

View file

@ -13,8 +13,9 @@ import { getNestedProperty } from '@kbn/ml-nested-property';
import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public';
import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
import { CreateDataViewButton } from '../../../../../components/create_data_view_button';
import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana';
import { useMlKibana, useMlManagementLocator } from '../../../../../contexts/kibana';
import { useToastNotificationService } from '../../../../../services/toast_notification_service';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import {
getDataViewAndSavedSearchCallback,
isCcsIndexPattern,
@ -33,7 +34,7 @@ export const SourceSelection: FC = () => {
uiSettings,
},
} = useMlKibana();
const navigateToPath = useNavigateToPath();
const mlManagementLocator = useMlManagementLocator();
const [isCcsCallOut, setIsCcsCallOut] = useState(false);
const [ccsCallOutBodyText, setCcsCallOutBodyText] = useState<string>();
@ -96,11 +97,12 @@ export const SourceSelection: FC = () => {
return;
}
await navigateToPath(
`/data_frame_analytics/new_job?${
await mlManagementLocator?.navigate({
sectionId: 'ml',
appId: `analytics/${ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB}?${
type === 'index-pattern' ? 'index' : 'savedSearchId'
}=${encodeURIComponent(id)}`
);
}=${encodeURIComponent(id)}`,
});
};
return (

View file

@ -6,26 +6,29 @@
*/
import type { FC } from 'react';
import React, { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import React, { useState, useCallback } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useUrlState, usePageUrlState, type ListingPageUrlState } from '@kbn/ml-url-state';
import { usePageUrlState, type ListingPageUrlState } from '@kbn/ml-url-state';
import { css } from '@emotion/react';
import { DataFrameAnalyticsList } from './components/analytics_list';
import { useRefreshInterval } from './components/analytics_list/use_refresh_interval';
import { NodeAvailableWarning } from '../../../components/node_available_warning';
import { SavedObjectsWarning } from '../../../components/saved_objects_warning';
import { UpgradeWarning } from '../../../components/upgrade';
import { JobMap } from '../job_map';
import { DataFrameAnalyticsListColumn } from './components/analytics_list/common';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { HelpMenu } from '../../../components/help_menu';
import { useMlKibana } from '../../../contexts/kibana';
import { useMlKibana, useMlManagementLocator } from '../../../contexts/kibana';
import { useRefreshAnalyticsList } from '../../common';
import { MlPageHeader } from '../../../components/page_header';
import { CreateAnalyticsButton } from './components/create_analytics_button/create_analytics_button';
import { usePermissionCheck } from '../../../capabilities/check_capabilities';
import { ExportJobsFlyout, ImportJobsFlyout } from '../../../components/import_export_jobs';
import { SynchronizeSavedObjectsButton } from '../../../jobs/jobs_list/components/top_level_actions/synchronize_saved_objects_button';
interface PageUrlState {
pageKey: typeof ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE;
pageKey: typeof ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE_FOR_URL;
pageUrlState: ListingPageUrlState;
}
@ -38,10 +41,9 @@ export const getDefaultDFAListState = (): ListingPageUrlState => ({
export const Page: FC = () => {
const [blockRefresh, setBlockRefresh] = useState(false);
const [globalState] = useUrlState('_g');
const [dfaPageState, setDfaPageState] = usePageUrlState<PageUrlState>(
ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE_FOR_URL,
getDefaultDFAListState()
);
@ -49,21 +51,53 @@ export const Page: FC = () => {
const [isLoading, setIsLoading] = useState(false);
const { refresh } = useRefreshAnalyticsList({ isLoading: setIsLoading });
const location = useLocation();
const selectedTabId = useMemo(() => location.pathname.split('/').pop(), [location]);
const mapJobId = globalState?.ml?.jobId;
const mapModelId = globalState?.ml?.modelId;
const {
services: { docLinks },
} = useMlKibana();
const helpLink = docLinks.links.ml.dataFrameAnalytics;
const mlManagementLocator = useMlManagementLocator();
const navigateToSourceSelection = useCallback(async () => {
await mlManagementLocator?.navigate({
sectionId: 'ml',
appId: `analytics/${ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION}`,
});
}, [mlManagementLocator]);
const canCreateAnalytics = usePermissionCheck('canCreateDataFrameAnalytics');
return (
<>
<MlPageHeader>
<FormattedMessage
id="xpack.ml.dataframe.analyticsList.title"
defaultMessage="Data Frame Analytics Jobs"
/>
<EuiFlexGroup direction="row" gutterSize="s" wrap={true}>
<EuiFlexItem grow={true} css={css({ minWidth: '400px' })}>
<FormattedMessage
id="xpack.ml.dataframe.analyticsList.title"
defaultMessage="Data Frame Analytics Jobs"
/>
</EuiFlexItem>
<EuiFlexItem grow={true} />
<EuiFlexItem grow={false} justifyContent="flexEnd">
<EuiFlexGroup direction="row" gutterSize="s">
<EuiFlexItem grow={false}>
<SynchronizeSavedObjectsButton refreshJobs={refresh} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ExportJobsFlyout
isDisabled={!canCreateAnalytics}
currentTab={'data-frame-analytics'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ImportJobsFlyout isDisabled={!canCreateAnalytics} onImportComplete={refresh} />
</EuiFlexItem>
<CreateAnalyticsButton
size="m"
navigateToSourceSelection={navigateToSourceSelection}
isDisabled={!canCreateAnalytics}
/>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</MlPageHeader>
<NodeAvailableWarning />
@ -71,16 +105,11 @@ export const Page: FC = () => {
<SavedObjectsWarning onCloseFlyout={refresh} forceRefresh={isLoading} />
<UpgradeWarning />
{selectedTabId === 'map' && (mapJobId || mapModelId) && (
<JobMap analyticsId={mapJobId} modelId={mapModelId} />
)}
{selectedTabId === 'data_frame_analytics' && (
<DataFrameAnalyticsList
blockRefresh={blockRefresh}
pageState={dfaPageState}
updatePageState={setDfaPageState}
/>
)}
<DataFrameAnalyticsList
blockRefresh={blockRefresh}
pageState={dfaPageState}
updatePageState={setDfaPageState}
/>
<HelpMenu docLink={helpLink} />
</>
);

View file

@ -24,6 +24,8 @@ import {
FlyoutType,
useJobInfoFlyouts,
} from '../../../../jobs/components/job_details_flyout/job_details_flyout_context';
import { useCreateAndNavigateToManagementMlLink } from '../../../../contexts/kibana/use_create_url';
import { usePermissionCheck } from '../../../../capabilities/check_capabilities';
interface Props {
setIsIdSelectorFlyoutVisible: React.Dispatch<React.SetStateAction<boolean>>;
selectedId?: string;
@ -92,9 +94,16 @@ export const AnalyticsIdSelectorControls: FC<Props> = ({
setIsIdSelectorFlyoutVisible,
selectedId,
}) => {
const [canGetDataFrameAnalytics, canCreateDataFrameAnalytics] = usePermissionCheck([
'canGetDataFrameAnalytics',
'canCreateDataFrameAnalytics',
]);
const redirectToDfaJobManagement = useCreateAndNavigateToManagementMlLink('', 'analytics');
return (
<>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
{selectedId ? (
<SelectorControl
@ -125,6 +134,31 @@ export const AnalyticsIdSelectorControls: FC<Props> = ({
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem />
{canGetDataFrameAnalytics ? (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
color="primary"
onClick={redirectToDfaJobManagement}
disabled={!canGetDataFrameAnalytics}
data-test-subj="mlJobSelectorManageJobsButton"
>
{canCreateDataFrameAnalytics ? (
<FormattedMessage
id="xpack.ml.jobSelector.manageJobsLinkLabel"
defaultMessage="Manage jobs"
/>
) : (
<FormattedMessage
id="xpack.ml.jobSelector.viewJobsLinkLabel"
defaultMessage="View jobs"
/>
)}
</EuiButtonEmpty>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
<EuiHorizontalRule />
</>

View file

@ -18,12 +18,13 @@ import {
type EuiThemeComputed,
} from '@elastic/eui';
import { JOB_MAP_NODE_TYPES } from '@kbn/ml-data-frame-analytics-utils';
import { useMlKibana, useMlLocator } from '../../../contexts/kibana';
import { useMlKibana } from '../../../contexts/kibana';
import { Controls, Cytoscape, JobMapLegend } from './components';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useRefresh } from '../../../routing/use_refresh';
import { useRefDimensions } from './components/use_ref_dimensions';
import { useFetchAnalyticsMapData } from './use_fetch_analytics_map_data';
import { useCreateAndNavigateToManagementMlLink } from '../../../contexts/kibana/use_create_url';
const getCytoscapeDivStyle = (theme: EuiThemeComputed) => ({
background: `linear-gradient(
@ -67,19 +68,15 @@ export const JobMap: FC<Props> = ({ defaultHeight, analyticsId, modelId, forceRe
} = useFetchAnalyticsMapData();
const {
services: {
notifications,
application: { navigateToUrl },
},
services: { notifications },
} = useMlKibana();
const locator = useMlLocator()!;
const { euiTheme } = useEuiTheme();
const refresh = useRefresh();
const redirectToAnalyticsManagementPage = async () => {
const url = await locator.getUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
await navigateToUrl(url);
};
const redirectToAnalyticsManagementPage = useCreateAndNavigateToManagementMlLink(
ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
'analytics'
);
const updateElements = (nodeId: string, nodeLabel: string, destIndexNode?: string) => {
// If removing the root job just go back to the jobs list

View file

@ -80,7 +80,7 @@ export const Page: FC = () => {
}
if (jobsExist === false) {
return <AnalyticsEmptyPrompt />;
return <AnalyticsEmptyPrompt showDocsLink />;
}
return (
<>

View file

@ -14,22 +14,19 @@ import {
EuiFlexGroup,
EuiFlexGrid,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiSpacer,
EuiText,
EuiBetaBadge,
EuiTextAlign,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useTimefilter } from '@kbn/ml-date-picker';
import { ENABLE_ESQL } from '@kbn/esql-utils';
import { isFullLicense } from '../license';
import { useMlKibana, useNavigateToPath } from '../contexts/kibana';
import { useMlKibana } from '../contexts/kibana';
import { HelpMenu } from '../components/help_menu';
import { MlPageHeader } from '../components/page_header';
import { ML_PAGES } from '../../locator';
import { DataVisualizerGrid } from '../overview/data_visualizer_grid';
function startTrialDescription() {
return (
@ -66,7 +63,6 @@ export const DatavisualizerSelector: FC = () => {
} = useMlKibana();
const isEsqlEnabled = useMemo(() => uiSettings.get(ENABLE_ESQL), [uiSettings]);
const helpLink = docLinks.links.ml.guide;
const navigateToPath = useNavigateToPath();
const startTrialVisible =
licenseManagement !== undefined &&
@ -78,7 +74,6 @@ export const DatavisualizerSelector: FC = () => {
console.error('File data visualizer plugin not available');
return null;
}
const maxFileSize = dataVisualizer.getMaxBytesFormatted();
return (
<>
@ -95,123 +90,14 @@ export const DatavisualizerSelector: FC = () => {
<EuiText color="subdued">
<FormattedMessage
id="xpack.ml.datavisualizer.selector.dataVisualizerDescription"
defaultMessage="The Machine Learning Data Visualizer tool helps you understand your data,
defaultMessage="The Machine Learning Data Visualizer tool helps you understand your data
by analyzing the metrics and fields in a log file or an existing Elasticsearch index."
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xl" />
<EuiFlexGrid gutterSize="xl" columns={2} style={{ maxWidth: '1000px' }}>
<EuiFlexItem>
<EuiCard
hasBorder
icon={<EuiIcon size="xxl" type="addDataApp" />}
title={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.importDataTitle"
defaultMessage="Visualize data from a file"
/>
}
description={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.importDataDescription"
defaultMessage="Import data from a log file. You can upload files up to {maxFileSize}."
values={{ maxFileSize }}
/>
}
footer={
<EuiButton
target="_self"
onClick={() => navigateToPath('/filedatavisualizer')}
data-test-subj="mlDataVisualizerUploadFileButton"
>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.uploadFileButtonLabel"
defaultMessage="Select file"
/>
</EuiButton>
}
data-test-subj="mlDataVisualizerCardImportData"
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCard
hasBorder
icon={<EuiIcon size="xxl" type="dataVisualizer" />}
title={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.selectDataViewTitle"
defaultMessage="Visualize data from a data view"
/>
}
description={''}
footer={
<EuiButton
target="_self"
onClick={() => navigateToPath('/datavisualizer_index_select')}
data-test-subj="mlDataVisualizerSelectIndexButton"
>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.selectDataViewButtonLabel"
defaultMessage="Select data view"
/>
</EuiButton>
}
data-test-subj="mlDataVisualizerCardIndexData"
/>
</EuiFlexItem>
{isEsqlEnabled ? (
<EuiFlexItem>
<EuiCard
hasBorder
icon={<EuiIcon size="xxl" type="dataVisualizer" />}
title={
<EuiTextAlign textAlign="center">
<>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.selectESQLTitle"
defaultMessage="Visualize data using ES|QL"
/>{' '}
<EuiBetaBadge
label=""
iconType="beaker"
size="m"
color="hollow"
tooltipContent={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.esqlTechnicalPreviewBadge.titleMsg"
defaultMessage="ES|QL data visualizer is in technical preview."
/>
}
tooltipPosition={'right'}
/>
</>
</EuiTextAlign>
}
description={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg"
defaultMessage="Use ES|QL queries to visualize information about any data set."
/>
}
footer={
<EuiButton
target="_self"
onClick={() => navigateToPath(ML_PAGES.DATA_VISUALIZER_ESQL)}
data-test-subj="mlDataVisualizerSelectESQLButton"
>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.useESQLButtonLabel"
defaultMessage="Use ES|QL"
/>
</EuiButton>
}
data-test-subj="mlDataVisualizerCardESQLData"
/>
</EuiFlexItem>
) : null}
</EuiFlexGrid>
<DataVisualizerGrid buttonType="full" isEsqlEnabled={isEsqlEnabled} />
{startTrialVisible === true && (
<Fragment>
<EuiSpacer size="xxl" />

View file

@ -27,6 +27,7 @@ import { checkPermission } from '../../capabilities/check_capabilities';
import { MlPageHeader } from '../../components/page_header';
import { useEnabledFeatures } from '../../contexts/ml';
import { TechnicalPreviewBadge } from '../../components/technical_preview_badge';
import { useMlManagementLocator } from '../../contexts/kibana/use_create_url';
export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) => {
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const {
@ -44,6 +45,7 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false })
const mlApi = useMlApi();
const { showNodeInfo } = useEnabledFeatures();
const mlLocator = useMlLocator()!;
const mlManagementLocator = useMlManagementLocator();
const mlFeaturesDisabled = !isFullLicense();
getMlNodeCount(mlApi);
@ -85,13 +87,12 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false })
icon: 'createAdvancedJob',
type: 'file',
getUrl: async () => {
return await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED,
pageState: {
index: dataViewId,
globalState,
},
});
return (
(await mlManagementLocator?.getRedirectUrl({
sectionId: 'ml',
appId: `anomaly_detection/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED}?index=${dataViewId}`,
})) ?? ''
);
},
canDisplay: async () => {
try {
@ -122,13 +123,13 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false })
icon: 'classificationJob',
type: 'file',
getUrl: async () => {
return await mlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB,
pageState: {
index: dataViewId,
globalState,
},
});
if (!mlManagementLocator) return '';
return (
(await mlManagementLocator?.getRedirectUrl({
sectionId: 'ml',
appId: `analytics/${ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB}?index=${dataViewId}`,
})) ?? ''
);
},
canDisplay: async () => {
return (
@ -153,13 +154,12 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false })
icon: m.logo?.icon ?? '',
type: 'index',
getUrl: async () => {
return await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER,
pageState: {
id: m.id,
index: dataViewId,
},
});
return (
(await mlManagementLocator?.getRedirectUrl({
sectionId: 'ml',
appId: `anomaly_detection/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER}?id=${m.id}&index=${dataViewId}`,
})) ?? ''
);
},
canDisplay: async () => {
try {

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import {
EuiFlyout,
EuiFlyoutHeader,
@ -22,12 +22,13 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import useMountedState from 'react-use/lib/useMountedState';
import type { CombinedJobWithStats } from '../../../../../common/types/anomaly_detection_jobs';
import { useMlApi, useMlLocator, useNavigateToPath } from '../../../contexts/kibana';
import { useMlApi } from '../../../contexts/kibana';
import { JobDetails } from '../../jobs_list/components/job_details';
import { loadFullJob } from '../../jobs_list/components/utils';
import { useToastNotificationService } from '../../../services/toast_notification_service';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useJobInfoFlyouts } from './job_details_flyout_context';
import { useCreateAndNavigateToManagementMlLink } from '../../../contexts/kibana/use_create_url';
const doNothing = () => {};
export const JobDetailsFlyout = () => {
@ -71,24 +72,17 @@ export const JobDetailsFlyout = () => {
fetchJobDetails();
}, [jobId, mlApi, displayErrorToast, isMounted]);
const navigateToPath = useNavigateToPath();
const mlLocator = useMlLocator();
const pageState = useMemo(() => ({ jobId }), [jobId]);
const openJobsList = useCreateAndNavigateToManagementMlLink(
ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
'anomaly_detection',
pageState
);
if (!jobId) {
return null;
}
const openJobsList = async () => {
const pageState = { jobId };
if (mlLocator) {
const url = await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState,
});
await navigateToPath(url);
}
};
return isDetailFlyoutOpen ? (
<EuiFlyout
data-test-subj="jobDetailsFlyout"

View file

@ -8,14 +8,18 @@
import type { FC } from 'react';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui';
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import adImage from './anomaly_detection_kibana.png';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { useMlKibana, useMlLocator, useNavigateToPath } from '../../../../contexts/kibana';
import { useMlKibana, useMlManagementLocator } from '../../../../contexts/kibana';
import { usePermissionCheck } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check';
import { MLEmptyPromptCard } from '../../../../components/overview/ml_empty_prompt_card';
export const AnomalyDetectionEmptyState: FC = () => {
export const AnomalyDetectionEmptyState: FC<{ showDocsLink?: boolean }> = ({
showDocsLink = false,
}) => {
const canCreateJob = usePermissionCheck('canCreateJob');
const disableCreateAnomalyDetectionJob = !canCreateJob || !mlNodesAvailable();
@ -23,59 +27,68 @@ export const AnomalyDetectionEmptyState: FC = () => {
services: { docLinks },
} = useMlKibana();
const mlLocator = useMlLocator();
const navigateToPath = useNavigateToPath();
const mlLocator = useMlManagementLocator();
const redirectToCreateJobSelectIndexPage = async () => {
if (!mlLocator) return;
const path = await mlLocator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX,
await mlLocator.navigate({
sectionId: 'ml',
appId: `anomaly_detection/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX}`,
});
await navigateToPath(path, true);
};
return (
<EuiEmptyPrompt
<MLEmptyPromptCard
layout="horizontal"
hasBorder={false}
hasBorder={true}
hasShadow={false}
icon={<EuiImage size="fullWidth" src={adImage} alt="anomaly_detection" />}
title={
<h2>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.createFirstJobMessage"
defaultMessage="Start detecting anomalies"
/>
</h2>
}
iconSrc={adImage}
iconAlt={i18n.translate('xpack.ml.overview.anomalyDetection.title', {
defaultMessage: 'Anomaly detection',
})}
title={i18n.translate('xpack.ml.overview.anomalyDetection.createFirstJobMessage', {
defaultMessage: 'Spot anomalies faster',
})}
body={
<>
<p>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.emptyPromptText"
defaultMessage="Anomaly detection enables you to find unusual behavior in time series data. Start automatically spotting the anomalies hiding in your data and resolve issues faster."
/>
</p>
</>
<p>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.emptyPromptText"
defaultMessage="Start automatically spotting anomalies hiding in your time series data and resolve issues faster."
/>
</p>
}
actions={[
<EuiButton
color="primary"
onClick={redirectToCreateJobSelectIndexPage}
isDisabled={disableCreateAnomalyDetectionJob}
data-test-subj="mlCreateNewJobButton"
>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.createJobButtonText"
defaultMessage="Create anomaly detection job"
/>
</EuiButton>,
<EuiLink href={docLinks.links.ml.anomalyDetection} target="_blank" external>
<FormattedMessage
id="xpack.ml.common.readDocumentationLink"
defaultMessage="Read documentation"
/>
</EuiLink>,
...[
<EuiButton
fill
color="primary"
onClick={redirectToCreateJobSelectIndexPage}
isDisabled={disableCreateAnomalyDetectionJob}
data-test-subj="mlCreateNewJobButton"
>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.createJobButtonText"
defaultMessage="Create anomaly detection job"
/>
</EuiButton>,
],
...(showDocsLink
? [
<EuiButtonEmpty
target="_blank"
href={docLinks.links.ml.anomalyDetection}
data-test-subj="mlAnalyticsReadDocumentationButton"
iconType="popout"
iconSide="left"
>
<FormattedMessage
id="xpack.ml.common.readDocumentationLink"
defaultMessage="Read documentation"
/>
</EuiButtonEmpty>,
]
: []),
]}
data-test-subj="mlAnomalyDetectionEmptyState"
/>

View file

@ -23,7 +23,7 @@ import { isManagedJob } from '../../../jobs_utils';
export function actionsMenuContent(
toastNotifications,
application,
share,
mlApi,
showEditJobFlyout,
showDatafeedChartFlyout,
@ -152,7 +152,7 @@ export function actionsMenuContent(
return isJobBlocked(item) === false && canCreateJob;
},
onClick: (item) => {
cloneJob(toastNotifications, application, mlApi, item.id);
cloneJob(toastNotifications, share, mlApi, item.id);
closeMenu(true);
},
'data-test-subj': 'mlActionButtonCloneJob',

View file

@ -78,7 +78,9 @@ export function extractJobDetails(job, basePath, refreshJobList) {
calendars.items = job.calendars.map((c) => [
'',
<EuiLink
href={basePath.prepend(`/app/ml/settings/calendars_list/edit_calendar/${c}?_g=()`)}
href={basePath.prepend(
`/app/management/ml/ad_settings/calendars_list/edit_calendar/${c}?_g=()`
)}
data-test-subj={`mlJobDetailsCalendar-${c}`}
>
{c}

View file

@ -364,7 +364,7 @@ export class JobsListUI extends Component {
}),
actions: actionsMenuContent(
this.props.kibana.services.notifications.toasts,
this.props.kibana.services.application,
this.props.kibana.services.share,
this.mlApi,
this.props.showEditJobFlyout,
this.props.showDatafeedChartFlyout,

View file

@ -21,7 +21,6 @@ import { DeleteJobModal } from '../delete_job_modal';
import { ResetJobModal } from '../reset_job_modal';
import { StartDatafeedModal } from '../start_datafeed_modal';
import { MultiJobActions } from '../multi_job_actions';
import { NewJobButton } from '../new_job_button';
import { JobStatsBar } from '../jobs_stats_bar';
import { NodeAvailableWarning } from '../../../../components/node_available_warning';
import { JobsAwaitingNodeWarning } from '../../../../components/jobs_awaiting_node_warning';
@ -41,6 +40,7 @@ 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';
import { DatePicker } from '../../../../components/ml_page/date_picker';
let blockingJobsRefreshTimeout = null;
@ -427,6 +427,10 @@ export class JobsListViewUI extends Component {
return BLOCKED_JOBS_REFRESH_INTERVAL_MS;
}
refreshJobs = () => {
this.refreshJobSummaryList();
};
renderJobsListComponents() {
const { isRefreshing, loading, jobsSummaryList, jobsAwaitingNodeCount } = this.state;
const jobIds = jobsSummaryList.map((j) => j.id);
@ -448,20 +452,21 @@ export class JobsListViewUI extends Component {
<>
<SpaceManagementContextWrapper>
{noJobsFound ? <AnomalyDetectionEmptyState /> : null}
{noJobsFound ? <AnomalyDetectionEmptyState showDocsLink /> : null}
{jobIds.length > 0 ? (
<>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
<JobStatsBar
jobsSummaryList={jobsSummaryList}
showNodeInfo={this.props.showNodeInfo}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<NewJobButton />
</EuiFlexItem>
<EuiFlexItem grow={true} />
<EuiFlexGroup justifyContent="flexEnd">
<DatePicker />
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />

View file

@ -8,23 +8,32 @@
import { usePermissionCheck } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import React from 'react';
import React, { useCallback } from 'react';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { useMlManagementLocator } from '../../../../contexts/kibana';
export function NewJobButton() {
export function NewJobButton({ size = 's' }) {
const canCreateJob = usePermissionCheck('canCreateJob');
const buttonEnabled = canCreateJob && mlNodesAvailable();
const newJob = useCreateAndNavigateToMlLink(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX);
const mlLocator = useMlManagementLocator();
const redirectToCreateJobSelectIndexPage = useCallback(async () => {
if (!mlLocator || !canCreateJob) return;
await mlLocator.navigate({
sectionId: 'ml',
appId: `anomaly_detection/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX}`,
});
}, [mlLocator, canCreateJob]);
return (
<EuiButton
data-test-subj="mlCreateNewJobButton"
onClick={newJob}
size="s"
onClick={redirectToCreateJobSelectIndexPage}
size={size}
disabled={buttonEnabled === false}
fill
iconType="plusInCircle"

View file

@ -0,0 +1,52 @@
/*
* 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 { EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useCreateAndNavigateToManagementMlLink } from '../../../../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../../../locator';
export const SuppliedConfigurationsButton = () => {
const redirectToSuppliedConfigurationsPage = useCreateAndNavigateToManagementMlLink(
ML_PAGES.SUPPLIED_CONFIGURATIONS,
'anomaly_detection'
);
return (
<EuiButtonEmpty
size="m"
iconType="listAdd"
onClick={redirectToSuppliedConfigurationsPage}
flush="left"
data-test-subj="mlSuppliedConfigurationsButton"
>
<FormattedMessage
id="xpack.ml.suppliedConfigurationsManagementLabel"
defaultMessage="Supplied configurations"
/>
</EuiButtonEmpty>
);
};
export const AnomalyDetectionSettingsButton = () => {
const redirectToAnomalyDetectionSettingsPage = useCreateAndNavigateToManagementMlLink(
'',
'ad_settings'
);
return (
<EuiButtonEmpty
size="m"
iconType="gear"
onClick={redirectToAnomalyDetectionSettingsPage}
flush="left"
data-test-subj="mlAnomalyDetectionSettingsButton"
>
<FormattedMessage id="xpack.ml.anomalyDetectionSettingsLabel" defaultMessage="Settings" />
</EuiButtonEmpty>
);
};

View file

@ -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.
*/
export {
SuppliedConfigurationsButton,
AnomalyDetectionSettingsButton,
} from './anomaly_detection_actions';
export { SynchronizeSavedObjectsButton } from './synchronize_saved_objects_button';

View file

@ -0,0 +1,50 @@
/*
* 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, { useMemo, useState } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { usePermissionCheck } from '../../../../capabilities/check_capabilities';
import { JobSpacesSyncFlyout } from '../../../../components/job_spaces_sync';
export const SynchronizeSavedObjectsButton = ({ refreshJobs }: { refreshJobs: () => void }) => {
const [showSyncFlyout, setShowSyncFlyout] = useState(false);
function onCloseSyncFlyout() {
if (typeof refreshJobs === 'function') {
refreshJobs();
}
setShowSyncFlyout(false);
}
const [canCreateJob, canCreateDataFrameAnalytics, canCreateTrainedModels] = usePermissionCheck([
'canCreateJob',
'canCreateDataFrameAnalytics',
'canCreateTrainedModels',
]);
const canSync = useMemo(
() => canCreateJob || canCreateDataFrameAnalytics || canCreateTrainedModels,
[canCreateDataFrameAnalytics, canCreateJob, canCreateTrainedModels]
);
return (
<>
<EuiButtonEmpty
disabled={!canSync}
size="m"
flush="left"
iconType="inputOutput"
onClick={() => setShowSyncFlyout(true)}
data-test-subj="mlStackMgmtSyncButton"
>
<FormattedMessage
id="xpack.ml.management.jobsList.syncFlyoutButton"
defaultMessage="Synchronize saved objects"
/>
</EuiButtonEmpty>
{showSyncFlyout && <JobSpacesSyncFlyout onClose={onCloseSyncFlyout} />}
</>
);
};

View file

@ -9,6 +9,7 @@ import { each } from 'lodash';
import { i18n } from '@kbn/i18n';
import { parseInterval } from '@kbn/ml-parse-interval';
import { MANAGEMENT_APP_LOCATOR } from '@kbn/deeplinks-management/constants';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import { stringMatch } from '../../../util/string_utils';
@ -17,7 +18,6 @@ import { JOB_ACTION } from '../../../../../common/constants/job_actions';
import { mlCalendarService } from '../../../services/calendar_service';
import { jobCloningService } from '../../../services/job_cloning_service';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { PLUGIN_ID } from '../../../../../common/constants/app';
import { CREATED_BY_LABEL } from '../../../../../common/constants/new_job';
export function loadFullJob(mlApi, jobId) {
@ -215,8 +215,13 @@ function showResults(toastNotifications, resp, action) {
}
}
export async function cloneJob(toastNotifications, application, mlApi, jobId) {
export async function cloneJob(toastNotifications, share, mlApi, jobId) {
try {
const managementLocator = share.url.locators.get(MANAGEMENT_APP_LOCATOR);
if (!managementLocator) {
throw new Error('Could not find management locator');
}
const [{ job: cloneableJob, datafeed }, originalJob] = await Promise.all([
loadJobForCloning(mlApi, jobId),
loadFullJob(mlApi, jobId),
@ -283,7 +288,10 @@ export async function cloneJob(toastNotifications, application, mlApi, jobId) {
jobCloningService.stashJobCloningData(tempJobCloningData);
application.navigateToApp(PLUGIN_ID, { path: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB });
await managementLocator.navigate({
sectionId: 'ml',
appId: `anomaly_detection/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB}`,
});
} catch (error) {
toastNotificationServiceProvider(toastNotifications).displayErrorToast(
error,

View file

@ -7,10 +7,11 @@
import type { FC } from 'react';
import React from 'react';
import { useEuiTheme } from '@elastic/eui';
import { EuiSpacer, useEuiTheme, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { usePageUrlState } from '@kbn/ml-url-state';
import type { ListingPageUrlState } from '@kbn/ml-url-state';
import { css } from '@emotion/react';
import { JobsListView } from './components/jobs_list_view';
import { ML_PAGES } from '../../../../common/constants/locator';
import { HelpMenu } from '../../components/help_menu';
@ -19,15 +20,26 @@ import { MlPageHeader } from '../../components/page_header';
import { HeaderMenuPortal } from '../../components/header_menu_portal';
import { JobsActionMenu } from '../components/jobs_action_menu';
import { useEnabledFeatures } from '../../contexts/ml';
import { getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
import {
AnomalyDetectionSettingsButton,
SuppliedConfigurationsButton,
SynchronizeSavedObjectsButton,
} from './components/top_level_actions';
import { NewJobButton } from './components/new_job_button';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
import { ImportJobsFlyout } from '../../components/import_export_jobs/import_jobs_flyout';
import { ExportJobsFlyout } from '../../components/import_export_jobs';
interface PageUrlState {
pageKey: typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE;
pageKey: typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE_FOR_URL;
pageUrlState: ListingPageUrlState;
}
interface JobsPageProps {
isMlEnabledInSpace?: boolean;
lastRefresh?: number;
refreshList: () => void;
}
export const getDefaultAnomalyDetectionJobsListState = (): ListingPageUrlState => ({
@ -37,27 +49,65 @@ export const getDefaultAnomalyDetectionJobsListState = (): ListingPageUrlState =
sortDirection: 'asc',
});
export const JobsPage: FC<JobsPageProps> = ({ isMlEnabledInSpace, lastRefresh }) => {
export const JobsPage: FC<JobsPageProps> = ({ isMlEnabledInSpace, lastRefresh, refreshList }) => {
const [pageState, setPageState] = usePageUrlState<PageUrlState>(
ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE_FOR_URL,
getDefaultAnomalyDetectionJobsListState()
);
const {
services: { docLinks },
services: {
docLinks,
mlServices: { mlApi },
},
} = useMlKibana();
const { euiTheme } = useEuiTheme();
getMlNodeCount(mlApi);
const { showNodeInfo } = useEnabledFeatures();
const helpLink = docLinks.links.ml.anomalyDetection;
const [canCreateJob] = usePermissionCheck(['canCreateJob']);
return (
<>
<MlPageHeader>
<FormattedMessage id="xpack.ml.jobsList.title" defaultMessage="Anomaly Detection Jobs" />
<EuiFlexGroup wrap={true}>
<EuiFlexItem grow={true} css={css({ minWidth: '400px' })}>
<FormattedMessage
id="xpack.ml.jobsList.title"
defaultMessage="Anomaly Detection Jobs"
/>
</EuiFlexItem>
<EuiFlexItem grow={true} />
<EuiFlexItem grow={false} justifyContent="flexEnd">
<EuiFlexGroup direction="row" gutterSize="s">
<EuiFlexItem grow={false}>
<SuppliedConfigurationsButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AnomalyDetectionSettingsButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SynchronizeSavedObjectsButton refreshJobs={refreshList} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ExportJobsFlyout isDisabled={!canCreateJob} currentTab={'anomaly-detector'} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ImportJobsFlyout isDisabled={!canCreateJob} onImportComplete={refreshList} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<NewJobButton size="m" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</MlPageHeader>
<HeaderMenuPortal>
<JobsActionMenu />
</HeaderMenuPortal>
<EuiSpacer size="m" />
<JobsListView
euiTheme={euiTheme}
isMlEnabledInSpace={isMlEnabledInSpace}
@ -65,6 +115,7 @@ export const JobsPage: FC<JobsPageProps> = ({ isMlEnabledInSpace, lastRefresh })
jobsViewState={pageState}
onJobsViewStateUpdate={setPageState}
showNodeInfo={showNodeInfo}
canCreateJob={canCreateJob}
/>
<HelpMenu docLink={helpLink} />
</>

View file

@ -21,6 +21,7 @@ import {
SPARSE_DATA_AGGREGATIONS,
} from '@kbn/ml-anomaly-utils';
import { cloneDeep } from 'lodash';
import type { MlLocatorParams } from '../../../../../../locator';
import { jobCloningService } from '../../../../../services/job_cloning_service';
import type {
Job,
@ -28,7 +29,6 @@ import type {
Detector,
} from '../../../../../../../common/types/anomaly_detection_jobs';
import type { NewJobCapsService } from '../../../../../services/new_job_capabilities/new_job_capabilities_service';
import type { NavigateToPath } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import type { JobCreatorType } from '..';
import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../../common/constants/new_job';
@ -237,29 +237,44 @@ export function isSparseDataJob(job: Job, datafeed: Datafeed): boolean {
return false;
}
export type NavigateToMlManagementLink = (
_page: string,
pageState?: MlLocatorParams['pageState']
) => Promise<void>;
export function convertToMultiMetricJob(
jobCreator: JobCreatorType,
navigateToPath: NavigateToPath
navigateToPath: NavigateToMlManagementLink
) {
jobCreator.createdBy = CREATED_BY_LABEL.MULTI_METRIC;
jobCreator.modelPlot = false;
jobCloningService.stashJobForCloning(jobCreator, true, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC, {
index: jobCreator.dataViewId,
});
}
export function convertToAdvancedJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
export function convertToAdvancedJob(
jobCreator: JobCreatorType,
navigateToPath: NavigateToMlManagementLink
) {
jobCreator.createdBy = null;
jobCloningService.stashJobForCloning(jobCreator, true, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED, {
index: jobCreator.dataViewId,
});
}
export function resetAdvancedJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
export function resetAdvancedJob(
jobCreator: JobCreatorType,
navigateToPath: NavigateToMlManagementLink
) {
jobCreator.createdBy = null;
jobCloningService.stashJobForCloning(jobCreator, true, false);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB);
}
export function resetJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
export function resetJob(jobCreator: JobCreatorType, navigateToPath: NavigateToMlManagementLink) {
jobCreator.jobId = '';
jobCloningService.stashJobForCloning(jobCreator, true, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB);
@ -267,12 +282,12 @@ export function resetJob(jobCreator: JobCreatorType, navigateToPath: NavigateToP
export function advancedStartDatafeed(
jobCreator: JobCreatorType | null,
navigateToPath: NavigateToPath
navigateToPath: NavigateToMlManagementLink
) {
if (jobCreator !== null) {
jobCloningService.stashJobForCloning(jobCreator, false, false);
}
navigateToPath('/jobs');
navigateToPath(ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE);
}
export function aggFieldPairsCanBeCharted(afs: AggFieldPair[]) {

View file

@ -21,7 +21,8 @@ import { KBN_FIELD_TYPES } from '@kbn/field-types';
import { ML_JOB_AGGREGATION } from '@kbn/ml-anomaly-utils';
import type { LensApi } from '@kbn/lens-plugin/public';
import type { DashboardApi } from '@kbn/dashboard-plugin/public';
import { ML_PAGES, ML_APP_LOCATOR } from '../../../../../common/constants/locator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { MlManagementLocatorInternal } from '../../../../locator/ml_management_locator';
export const COMPATIBLE_SERIES_TYPES = [
'line',
@ -51,9 +52,9 @@ export async function redirectToADJobWizards(
lens: LensPublicStart
) {
const { query, filters, to, from, vis } = await getJobsItemsFromEmbeddable(embeddable, lens);
const locator = share.url.locators.get(ML_APP_LOCATOR);
const locator = new MlManagementLocatorInternal(share);
const url = await locator?.getUrl({
const { url } = await locator?.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_LENS,
pageState: {
vis: vis as unknown as SerializableRecord,

View file

@ -11,7 +11,8 @@ import { apiIsOfType } from '@kbn/presentation-publishing';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { MapApi } from '@kbn/maps-plugin/public';
import type { DashboardApi } from '@kbn/dashboard-plugin/public';
import { ML_PAGES, ML_APP_LOCATOR } from '../../../../../common/constants/locator';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { MlManagementLocatorInternal } from '../../../../locator/ml_management_locator';
export async function redirectToGeoJobWizard(
embeddable: MapApi,
@ -24,7 +25,7 @@ export async function redirectToGeoJobWizard(
const { query, filters, to, from } = await getJobsItemsFromEmbeddable(embeddable);
const embeddableQuery = embeddable.query$?.value;
const embeddableFilters = embeddable.filters$?.value ?? [];
const locator = share.url.locators.get(ML_APP_LOCATOR);
const locator = new MlManagementLocatorInternal(share);
const pageState = {
dashboard: { query, filters },
@ -37,7 +38,7 @@ export async function redirectToGeoJobWizard(
...(layerQuery ? { layer: { query: layerQuery } } : {}),
};
const url = await locator?.getUrl({
const { url } = await locator?.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_MAP,
pageState,
});

View file

@ -9,9 +9,9 @@ import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { Query, TimeRange } from '@kbn/es-query';
import { ML_APP_LOCATOR } from '../../../../../common/constants/locator';
import { ML_PAGES } from '../../../../locator';
import type { CategorizationType } from './quick_create_job';
import { MlManagementLocatorInternal } from '../../../../locator/ml_management_locator';
export async function redirectToADJobWizards(
categorizationType: CategorizationType,
@ -23,9 +23,9 @@ export async function redirectToADJobWizards(
timeRange: TimeRange,
share: SharePluginStart
) {
const locator = share.url.locators.get(ML_APP_LOCATOR)!;
const locator = new MlManagementLocatorInternal(share);
const url = await locator.getUrl({
const { url } = await locator.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS,
pageState: {
categorizationType,

View file

@ -27,6 +27,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import { useNavigateToManagementMlLink } from '../../../../../../../contexts/kibana/use_create_url';
import { JobCreatorContext } from '../../../job_creator_context';
import type { AdvancedJobCreator } from '../../../../../common/job_creator';
import { resetAdvancedJob } from '../../../../../common/job_creator/util/general';
@ -36,7 +37,7 @@ import type {
} from '../../../../../../../../../common/types/anomaly_detection_jobs';
import type { DatafeedValidationResponse } from '../../../../../../../../../common/types/job_validation';
import { useMlKibana, useMlApi, useNavigateToPath } from '../../../../../../../contexts/kibana';
import { useMlKibana, useMlApi } from '../../../../../../../contexts/kibana';
const fixedPageSize: number = 8;
@ -57,7 +58,7 @@ export const ChangeDataViewModal: FC<Props> = ({ onClose }) => {
uiSettings,
},
} = useMlKibana();
const navigateToPath = useNavigateToPath();
const navigateToMlManagementLink = useNavigateToManagementMlLink('anomaly_detection');
const { validateDatafeedPreview } = useMlApi();
const { jobCreator: jc } = useContext(JobCreatorContext);
@ -119,9 +120,8 @@ export const ChangeDataViewModal: FC<Props> = ({ onClose }) => {
const applyDataView = useCallback(() => {
const newIndices = newDataViewTitle.split(',');
jobCreator.indices = newIndices;
resetAdvancedJob(jobCreator, navigateToPath);
// exclude mlJobService from deps
}, [jobCreator, newDataViewTitle, navigateToPath]);
resetAdvancedJob(jobCreator, navigateToMlManagementLink);
}, [jobCreator, newDataViewTitle, navigateToMlManagementLink]);
return (
<>

View file

@ -26,23 +26,19 @@ import {
} from '../../../../../../../../../settings/calendars/dst_utils';
import { JobCreatorContext } from '../../../../../job_creator_context';
import { Description } from './description';
import { PLUGIN_ID } from '../../../../../../../../../../../common/constants/app';
import type { MlCalendar } from '../../../../../../../../../../../common/types/calendars';
import { useMlApi, useMlKibana } from '../../../../../../../../../contexts/kibana';
import { useMlApi } from '../../../../../../../../../contexts/kibana';
import { GLOBAL_CALENDAR } from '../../../../../../../../../../../common/constants/calendars';
import { ML_PAGES } from '../../../../../../../../../../../common/constants/locator';
import { DescriptionDst } from './description_dst';
import { useMlManagementLink } from '../../../../../../../../../contexts/kibana/use_create_url';
import { MANAGEMENT_SECTION_IDS } from '../../../../../../../../../management';
interface Props {
isDst?: boolean;
}
export const CalendarsSelection: FC<Props> = ({ isDst = false }) => {
const {
services: {
application: { getUrlForApp },
},
} = useMlKibana();
const mlApi = useMlApi();
const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext);
@ -90,9 +86,10 @@ export const CalendarsSelection: FC<Props> = ({ isDst = false }) => {
},
};
const manageCalendarsHref = getUrlForApp(PLUGIN_ID, {
path: isDst ? ML_PAGES.CALENDARS_DST_MANAGE : ML_PAGES.CALENDARS_MANAGE,
});
const manageCalendarsHref = useMlManagementLink(
isDst ? ML_PAGES.CALENDARS_DST_MANAGE : ML_PAGES.CALENDARS_MANAGE,
MANAGEMENT_SECTION_IDS.AD_SETTINGS
);
const Desc = isDst ? DescriptionDst : Description;

View file

@ -10,14 +10,13 @@ import React, { Fragment, useContext } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
import { useNavigateToPath } from '../../../../../../../contexts/kibana';
import { convertToMultiMetricJob } from '../../../../../common/job_creator/util/general';
import { JobCreatorContext } from '../../../job_creator_context';
import { BucketSpan } from '../bucket_span';
import { SparseDataSwitch } from '../sparse_data';
import { useNavigateToManagementMlLink } from '../../../../../../../contexts/kibana/use_create_url';
interface Props {
setIsValid: (proceed: boolean) => void;
@ -25,10 +24,10 @@ interface Props {
export const SingleMetricSettings: FC<Props> = ({ setIsValid }) => {
const { jobCreator } = useContext(JobCreatorContext);
const navigateToPath = useNavigateToPath();
const navigateToMlManagement = useNavigateToManagementMlLink('anomaly_detection');
const convertToMultiMetric = () => {
convertToMultiMetricJob(jobCreator, navigateToPath);
convertToMultiMetricJob(jobCreator, navigateToMlManagement);
};
return (

View file

@ -18,6 +18,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useNavigateToManagementMlLink } from '../../../../../contexts/kibana/use_create_url';
import { createResultsUrl } from '../../../../../util/results_url';
import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana';
import { PreviousButton } from '../wizard_nav';
@ -51,7 +52,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
} = useMlKibana();
const navigateToPath = useNavigateToPath();
const navigateToMlManagement = useNavigateToManagementMlLink('anomaly_detection');
const { jobCreator, jobValidator, jobValidatorUpdated, resultsLoader } =
useContext(JobCreatorContext);
const [progress, setProgress] = useState(resultsLoader.progress);
@ -107,7 +108,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
try {
await jobCreator.createJob();
await jobCreator.createDatafeed();
advancedStartDatafeed(showStartModal ? jobCreator : null, navigateToPath);
advancedStartDatafeed(showStartModal ? jobCreator : null, navigateToMlManagement);
} catch (error) {
handleJobCreationError(error);
}
@ -135,11 +136,11 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
}
function clickResetJob() {
resetJob(jobCreator, navigateToPath);
resetJob(jobCreator, navigateToMlManagement);
}
const convertToAdvanced = () => {
convertToAdvancedJob(jobCreator, navigateToPath);
convertToAdvancedJob(jobCreator, navigateToMlManagement);
};
useEffect(() => {

View file

@ -6,44 +6,51 @@
*/
import type { FC } from 'react';
import React, { useCallback } from 'react';
import React from 'react';
import { EuiFlexGroup, EuiPageBody, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public';
import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
import { CreateDataViewButton } from '../../../../components/create_data_view_button';
import { useMlKibana, useNavigateToPath } from '../../../../contexts/kibana';
import {
useMlKibana,
useNavigateToPath,
useMlManagementLocator,
} from '../../../../contexts/kibana';
import { MlPageHeader } from '../../../../components/page_header';
export interface PageProps {
nextStepPath: string;
extraButtons?: React.ReactNode;
}
const RESULTS_PER_PAGE = 20;
type SavedObject = SavedObjectCommon<FinderAttributes & { isTextBasedQuery?: boolean }>;
export const Page: FC<PageProps> = ({
nextStepPath,
extraButtons,
}: {
nextStepPath: string;
extraButtons?: React.ReactNode;
}) => {
export const Page: FC<PageProps> = ({ nextStepPath, extraButtons }) => {
const { contentManagement, uiSettings } = useMlKibana().services;
const mlLocator = useMlManagementLocator();
const navigateToPath = useNavigateToPath();
const onObjectSelection = useCallback(
(id: string, type: string, name?: string) => {
const onObjectSelection = async (id: string, type: string, name?: string) => {
const urlPath = window.location.pathname;
if (urlPath.includes('management')) {
await mlLocator?.navigate({
sectionId: 'ml',
appId: `anomaly_detection/${nextStepPath}?${
type === 'index-pattern' ? 'index' : 'savedSearchId'
}=${encodeURIComponent(id)}`,
});
} else {
navigateToPath(
`${nextStepPath}?${
type === 'index-pattern' ? 'index' : 'savedSearchId'
}=${encodeURIComponent(id)}`
);
},
[navigateToPath, nextStepPath]
);
}
};
return (
<div data-test-subj="mlPageSourceSelection">

View file

@ -26,7 +26,7 @@ export async function preConfiguredJobRedirect(
try {
const redirectUrl = await getWizardUrlFromCloningJob(createdBy, dataViewId);
await navigateToUrl(`${basePath}/app/ml/${redirectUrl}`);
await navigateToUrl(`${basePath}/app/management/ml/anomaly_detection/${redirectUrl}`);
return Promise.reject();
} catch (error) {
return Promise.resolve();

View file

@ -19,7 +19,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { useMlKibana, useNavigateToPath } from '../../../../contexts/kibana';
import { useMlKibana, useMlManagementLocator } from '../../../../contexts/kibana';
import { useDataSource } from '../../../../contexts/ml';
import { DataRecognizer } from '../../../../components/data_recognizer';
@ -39,7 +39,6 @@ export const Page: FC = () => {
} = useMlKibana();
const dataSourceContext = useDataSource();
const navigateToPath = useNavigateToPath();
const onSelectDifferentIndex = useCreateAndNavigateToMlLink(
ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX
);
@ -50,6 +49,17 @@ export const Page: FC = () => {
const isTimeBasedIndex: boolean = selectedDataView.isTimeBased();
const mlManagementLocator = useMlManagementLocator();
const navigateToManagementPath = async (path: string) => {
if (!mlManagementLocator) return;
await mlManagementLocator.navigate({
sectionId: 'ml',
appId: `anomaly_detection${path}`,
});
};
useEffect(() => {
if (!isTimeBasedIndex) {
toasts.addWarning({
@ -137,12 +147,12 @@ export const Page: FC = () => {
dataVisualizerLink,
recentlyAccessed
);
navigateToPath(`/jobs/new_job/datavisualizer${getUrlParams()}`);
navigateToManagementPath(`/jobs/new_job/datavisualizer${getUrlParams()}`);
};
const jobTypes = [
{
onClick: () => navigateToPath(`/jobs/new_job/single_metric${getUrlParams()}`),
onClick: () => navigateToManagementPath(`/jobs/new_job/single_metric${getUrlParams()}`),
icon: {
type: 'createSingleMetricJob',
ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.singleMetricAriaLabel', {
@ -158,7 +168,7 @@ export const Page: FC = () => {
id: 'mlJobTypeLinkSingleMetricJob',
},
{
onClick: () => navigateToPath(`/jobs/new_job/multi_metric${getUrlParams()}`),
onClick: () => navigateToManagementPath(`/jobs/new_job/multi_metric${getUrlParams()}`),
icon: {
type: 'createMultiMetricJob',
ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.multiMetricAriaLabel', {
@ -175,7 +185,7 @@ export const Page: FC = () => {
id: 'mlJobTypeLinkMultiMetricJob',
},
{
onClick: () => navigateToPath(`/jobs/new_job/population${getUrlParams()}`),
onClick: () => navigateToManagementPath(`/jobs/new_job/population${getUrlParams()}`),
icon: {
type: 'createPopulationJob',
ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.populationAriaLabel', {
@ -192,7 +202,7 @@ export const Page: FC = () => {
id: 'mlJobTypeLinkPopulationJob',
},
{
onClick: () => navigateToPath(`/jobs/new_job/advanced${getUrlParams()}`),
onClick: () => navigateToManagementPath(`/jobs/new_job/advanced${getUrlParams()}`),
icon: {
type: 'createAdvancedJob',
ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.advancedAriaLabel', {
@ -209,7 +219,7 @@ export const Page: FC = () => {
id: 'mlJobTypeLinkAdvancedJob',
},
{
onClick: () => navigateToPath(`/jobs/new_job/categorization${getUrlParams()}`),
onClick: () => navigateToManagementPath(`/jobs/new_job/categorization${getUrlParams()}`),
icon: {
type: 'createGenericJob',
ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.categorizationAriaLabel', {
@ -225,7 +235,7 @@ export const Page: FC = () => {
id: 'mlJobTypeLinkCategorizationJob',
},
{
onClick: () => navigateToPath(`/jobs/new_job/rare${getUrlParams()}`),
onClick: () => navigateToManagementPath(`/jobs/new_job/rare${getUrlParams()}`),
icon: {
type: 'createGenericJob',
ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.rareAriaLabel', {
@ -244,7 +254,7 @@ export const Page: FC = () => {
if (hasGeoFields) {
jobTypes.push({
onClick: () => navigateToPath(`/jobs/new_job/geo${getUrlParams()}`),
onClick: () => navigateToManagementPath(`/jobs/new_job/geo${getUrlParams()}`),
icon: {
type: 'createGeoJob',
ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.geoAriaLabel', {

View file

@ -11,37 +11,180 @@ import type { CoreSetup } from '@kbn/core/public';
import type { ManagementSetup } from '@kbn/management-plugin/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import type { MlFeatures } from '../../../common/constants/app';
import type { MlCapabilities } from '../../../common/types/capabilities';
import type { MlFeatures, NLPSettings, ExperimentalFeatures } from '../../../common/constants/app';
import type { MlStartDependencies } from '../../plugin';
import type { ITelemetryClient } from '../services/telemetry/types';
export function registerManagementSection(
export enum MANAGEMENT_SECTION_IDS {
OVERVIEW = 'overview',
ANOMALY_DETECTION = 'anomaly_detection',
ANALYTICS = 'analytics',
TRAINED_MODELS = 'trained_models',
AD_SETTINGS = 'ad_settings',
}
export type ManagementSectionId = `${MANAGEMENT_SECTION_IDS}`;
const MANAGED_SECTIONS_SERVERLESS_CHECK: Record<
ManagementSectionId,
(mlFeatures: MlFeatures, isServerless: boolean, mlCapabilities: MlCapabilities) => boolean
> = {
[MANAGEMENT_SECTION_IDS.OVERVIEW]: (
mlFeatures: MlFeatures,
isServerless: boolean,
mlCapabilities: MlCapabilities
) => {
const isEsProject = !mlFeatures.ad && !mlFeatures.dfa && mlFeatures.nlp;
if (isEsProject) return true;
return (
// Can see Memory Usage & Notifications
mlCapabilities.canViewMlNodes ||
(mlFeatures.nlp && mlCapabilities.canGetTrainedModels) ||
(mlFeatures.dfa && mlCapabilities.canGetDataFrameAnalytics) ||
(mlFeatures.ad && mlCapabilities.canGetJobs)
);
},
[MANAGEMENT_SECTION_IDS.ANOMALY_DETECTION]: (
mlFeatures: MlFeatures,
isServerless: boolean,
mlCapabilities: MlCapabilities
) => {
return mlFeatures.ad && mlCapabilities.isADEnabled && mlCapabilities.canGetJobs;
},
[MANAGEMENT_SECTION_IDS.ANALYTICS]: (
mlFeatures: MlFeatures,
isServerless: boolean,
mlCapabilities: MlCapabilities
) => {
const isEsProject = !mlFeatures.ad && !mlFeatures.dfa && mlFeatures.nlp;
if (isEsProject) return false;
return mlFeatures.dfa && mlCapabilities.isDFAEnabled && mlCapabilities.canGetDataFrameAnalytics;
},
[MANAGEMENT_SECTION_IDS.TRAINED_MODELS]: (
mlFeatures: MlFeatures,
isServerless: boolean,
mlCapabilities: MlCapabilities
) => {
const isEsProject = isServerless && !mlFeatures.ad && !mlFeatures.dfa && mlFeatures.nlp;
if (isEsProject) return true;
return (mlFeatures.nlp || mlFeatures.dfa) && mlCapabilities.canGetTrainedModels;
},
[MANAGEMENT_SECTION_IDS.AD_SETTINGS]: (
mlFeatures: MlFeatures,
isServerless: boolean,
mlCapabilities: MlCapabilities
) => {
return mlFeatures.ad && mlCapabilities.isADEnabled && mlCapabilities.canGetJobs;
},
};
export const MANAGEMENT_SECTIONS: Record<ManagementSectionId, string> = {
[MANAGEMENT_SECTION_IDS.OVERVIEW]: i18n.translate('xpack.ml.management.overviewTitle', {
defaultMessage: 'Overview',
}),
[MANAGEMENT_SECTION_IDS.ANOMALY_DETECTION]: i18n.translate(
'xpack.ml.management.anomalyDetectionJobsTitle',
{
defaultMessage: 'Anomaly Detection Jobs',
}
),
[MANAGEMENT_SECTION_IDS.ANALYTICS]: i18n.translate(
'xpack.ml.management.dataFrameAnalyticsJobsTitle',
{
defaultMessage: 'Data Frame Analytics Jobs',
}
),
[MANAGEMENT_SECTION_IDS.TRAINED_MODELS]: i18n.translate(
'xpack.ml.management.trainedModelsTitle',
{
defaultMessage: 'Trained Models',
}
),
[MANAGEMENT_SECTION_IDS.AD_SETTINGS]: i18n.translate('xpack.ml.management.settingsTitle', {
defaultMessage: 'Anomaly Detection Settings',
}),
};
export function registerManagementSections(
management: ManagementSetup,
core: CoreSetup<MlStartDependencies>,
deps: { usageCollection?: UsageCollectionSetup },
deps: { usageCollection?: UsageCollectionSetup; telemetry?: ITelemetryClient },
isServerless: boolean,
mlFeatures: MlFeatures
mlFeatures: MlFeatures,
nlpSettings: NLPSettings,
experimentalFeatures: ExperimentalFeatures,
mlCapabilities: MlCapabilities
) {
const appName = i18n.translate('xpack.ml.management.jobsListTitle', {
defaultMessage: 'Machine Learning',
});
Object.keys(MANAGEMENT_SECTIONS).forEach((id) => {
const checkPermissionFn = MANAGED_SECTIONS_SERVERLESS_CHECK[id as ManagementSectionId];
return management.sections.section.insightsAndAlerting.registerApp({
id: 'jobsListLink',
title: appName,
order: 4,
async mount(params: ManagementAppMountParams) {
const [{ chrome }] = await core.getStartServices();
const { docTitle } = chrome;
if (!checkPermissionFn) {
throw new Error(`Unable to check permission for ML management section ${id}`);
}
const shouldShowSection = checkPermissionFn(mlFeatures, isServerless, mlCapabilities);
if (!shouldShowSection) return;
docTitle.change(appName);
const sectionId = id as ManagementSectionId;
const sectionTitle = MANAGEMENT_SECTIONS[sectionId];
management.sections.section.machineLearning
.registerApp({
id: sectionId,
title: sectionTitle,
order: 1,
async mount(params: ManagementAppMountParams) {
const [coreStart, pluginsStart] = await core.getStartServices();
const {
chrome: { docTitle },
} = coreStart;
const { mountApp } = await import('./jobs_list');
const unmountAppCallback = await mountApp(core, params, deps, isServerless, mlFeatures);
docTitle.change(sectionTitle);
return () => {
docTitle.reset();
unmountAppCallback();
};
},
const mlDeps = {
cases: pluginsStart.cases,
charts: pluginsStart.charts,
contentManagement: pluginsStart.contentManagement,
dashboard: pluginsStart.dashboard,
data: pluginsStart.data,
dataViewEditor: pluginsStart.dataViewEditor,
dataVisualizer: pluginsStart.dataVisualizer,
fieldFormats: pluginsStart.fieldFormats,
lens: pluginsStart.lens,
licensing: pluginsStart.licensing,
maps: pluginsStart.maps,
observabilityAIAssistant: pluginsStart.observabilityAIAssistant,
presentationUtil: pluginsStart.presentationUtil,
savedObjectsManagement: pluginsStart.savedObjectsManagement,
savedSearch: pluginsStart.savedSearch,
security: pluginsStart.security,
share: pluginsStart.share,
triggersActionsUi: pluginsStart.triggersActionsUi,
uiActions: pluginsStart.uiActions,
unifiedSearch: pluginsStart.unifiedSearch,
spaces: pluginsStart.spaces,
...deps,
};
const { mountApp } = await import('./mount_management_app');
const unmountAppCallback = await mountApp(
core,
params,
mlDeps,
isServerless,
mlFeatures,
experimentalFeatures,
nlpSettings,
sectionId
);
return () => {
docTitle.reset();
unmountAppCallback();
};
},
hideFromSidebar: sectionId === MANAGEMENT_SECTION_IDS.AD_SETTINGS,
})
.enable();
});
}

View file

@ -11,7 +11,6 @@ import { Router } from '@kbn/shared-ux-router';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import type { CoreStart } from '@kbn/core/public';
import {
EuiButtonEmpty,
EuiFlexGroup,
@ -35,7 +34,10 @@ import { getMlGlobalServices } from '../../../../util/get_services';
import { EnabledFeaturesContextProvider } from '../../../../contexts/ml';
import { type MlFeatures, PLUGIN_ID } from '../../../../../../common/constants/app';
import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check_capabilities';
import {
checkGetManagementMlJobsResolver,
usePermissionCheck,
} from '../../../../capabilities/check_capabilities';
import { AccessDeniedPage } from '../access_denied_page';
import { InsufficientLicensePage } from '../insufficient_license_page';
@ -98,6 +100,7 @@ export const JobsListPage: FC<Props> = ({
setInitialized(true);
};
const [canCreateJob] = usePermissionCheck(['canCreateJob']);
useEffect(() => {
check();
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -205,29 +208,35 @@ export const JobsListPage: FC<Props> = ({
>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<>
<EuiButtonEmpty
onClick={() => setShowSyncFlyout(true)}
data-test-subj="mlStackMgmtSyncButton"
>
{i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', {
defaultMessage: 'Synchronize saved objects',
})}
</EuiButtonEmpty>
{showSyncFlyout && <JobSpacesSyncFlyout onClose={onCloseSyncFlyout} />}
<EuiSpacer size="s" />
</>
{
<>
<EuiButtonEmpty
disabled={!canCreateJob}
onClick={() => setShowSyncFlyout(true)}
data-test-subj="mlStackMgmtSyncButton"
>
{i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', {
defaultMessage: 'Synchronize saved objects',
})}
</EuiButtonEmpty>
{showSyncFlyout && <JobSpacesSyncFlyout onClose={onCloseSyncFlyout} />}
<EuiSpacer size="s" />
</>
}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ExportJobsFlyout
isDisabled={false}
isDisabled={!canCreateJob}
currentTab={
currentTabId === 'trained-model' ? 'anomaly-detector' : currentTabId
}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ImportJobsFlyout isDisabled={false} onImportComplete={refreshJobs} />
<ImportJobsFlyout
isDisabled={!canCreateJob}
onImportComplete={refreshJobs}
/>
</EuiFlexItem>
</EuiFlexGroup>
<SpaceManagement

View file

@ -261,7 +261,7 @@ export const RefreshButton: FC<{ onRefreshClick: () => void; isRefreshing: boole
isRefreshing,
}) => (
<EuiButtonEmpty
data-test-subj={`mlRefreshJobListButton${isRefreshing ? ' loading' : ' loaded'}`}
data-test-subj={`mlDatePickerRefreshPageButton${isRefreshing ? ' loading' : ' loaded'}`}
onClick={onRefreshClick}
isLoading={isRefreshing}
iconType={'refresh'}

View file

@ -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 ReactDOM, { unmountComponentAtNode } from 'react-dom';
import React from 'react';
import type { CoreSetup } from '@kbn/core/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import type { MlFeatures, NLPSettings, ExperimentalFeatures } from '../../../common/constants/app';
import type { MlStartDependencies } from '../../plugin';
import { App } from '../app';
import type { ManagementSectionId } from '.';
const renderApp = (
coreStart: CoreStart,
params: ManagementAppMountParams,
deps: any,
isServerless: boolean,
mlFeatures: MlFeatures,
experimentalFeatures: ExperimentalFeatures,
nlpSettings: NLPSettings,
entryPoint: ManagementSectionId
) => {
ReactDOM.render(
React.createElement(App, {
coreStart,
deps,
appMountParams: params,
isServerless,
mlFeatures,
experimentalFeatures,
nlpSettings,
entryPoint,
}),
params.element
);
return () => {
unmountComponentAtNode(params.element);
deps.data.search.session.clear();
};
};
export async function mountApp(
core: CoreSetup<MlStartDependencies>,
params: ManagementAppMountParams,
deps: { usageCollection?: UsageCollectionSetup },
isServerless: boolean,
mlFeatures: MlFeatures,
experimentalFeatures: ExperimentalFeatures,
nlpSettings: NLPSettings,
entryPoint: ManagementSectionId
) {
const [coreStart] = await core.getStartServices();
return renderApp(
coreStart,
params,
deps,
isServerless,
mlFeatures,
experimentalFeatures,
nlpSettings,
entryPoint
);
}

View file

@ -16,14 +16,13 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { useElasticChartsTheme } from '@kbn/charts-theme';
import type { MemoryUsageInfo } from '../../../../common/types/trained_models';
import type { JobType, MlSavedObjectType } from '../../../../common/types/saved_objects';
import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models';
import { LoadingWrapper } from '../../jobs/new_job/pages/components/charts/loading_wrapper';
import { useFieldFormatter } from '../../contexts/kibana';
import { useRefresh } from '../../routing/use_refresh';
import { getMemoryItemColor } from '../memory_item_colors';
import { useToastNotificationService } from '../../services/toast_notification_service';
import { useEnabledFeatures } from '../../contexts/ml';
import { useMemoryUsage } from '../use_memory_usage';
interface Props {
node?: string;
@ -60,13 +59,10 @@ export const JobMemoryTreeMap: FC<Props> = ({ node, type, height }) => {
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
const { displayErrorToast } = useToastNotificationService();
const refresh = useRefresh();
const { loading, data: allData, error } = useMemoryUsage(node, type);
const chartHeight = height ?? DEFAULT_CHART_HEIGHT;
const trainedModelsApiService = useTrainedModelsApiService();
const [allData, setAllData] = useState<MemoryUsageInfo[]>([]);
const [data, setData] = useState<MemoryUsageInfo[]>([]);
const [loading, setLoading] = useState(false);
const [selectedOptions, setSelectedOptions] = useState<EuiComboBoxOptionOption[] | null>(null);
const typeOptions = useMemo(() => {
return Object.entries(TYPE_LABELS)
@ -107,21 +103,19 @@ export const JobMemoryTreeMap: FC<Props> = ({ node, type, height }) => {
[selectedOptions]
);
const loadJobMemorySize = useCallback(async () => {
setLoading(true);
try {
const resp = await trainedModelsApiService.memoryUsage(type, node);
setAllData(resp);
} catch (error) {
displayErrorToast(
error,
i18n.translate('xpack.ml.memoryUsage.treeMap.fetchFailedErrorMessage', {
defaultMessage: 'Error loading model memory usage data',
})
);
}
setLoading(false);
}, [trainedModelsApiService, type, node, displayErrorToast]);
useEffect(
function handleError() {
if (error) {
displayErrorToast(
error,
i18n.translate('xpack.ml.memoryUsage.treeMap.fetchFailedErrorMessage', {
defaultMessage: 'Error loading model memory usage data',
})
);
}
},
[error, displayErrorToast]
);
useEffect(
function redrawOnFilterChange() {
@ -130,12 +124,6 @@ export const JobMemoryTreeMap: FC<Props> = ({ node, type, height }) => {
[selectedOptions, allData, filterData]
);
useEffect(
function updateOnTimerRefresh() {
loadJobMemorySize();
},
[loadJobMemorySize, refresh]
);
return (
<div
style={{ height: chartHeight }}

View file

@ -1,76 +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 { FC } from 'react';
import React, { useCallback, useState } from 'react';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import { EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { NodesList } from './nodes_overview';
import { MlPageHeader } from '../components/page_header';
import { MemoryPage, JobMemoryTreeMap } from './memory_tree_map';
import { SavedObjectsWarning } from '../components/saved_objects_warning';
import { useEnabledFeatures } from '../contexts/ml';
enum TAB {
NODES,
MEMORY_USAGE,
}
export const MemoryUsagePage: FC = () => {
const [selectedTab, setSelectedTab] = useState<TAB>(TAB.NODES);
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true });
const { showNodeInfo } = useEnabledFeatures();
const refresh = useCallback(() => {
mlTimefilterRefresh$.next({
lastRefresh: Date.now(),
});
}, []);
return (
<>
<MlPageHeader>
<EuiFlexGroup responsive={false} wrap={false} alignItems={'center'} gutterSize={'m'}>
<EuiFlexItem grow={false}>
<FormattedMessage
id="xpack.ml.memoryUsage.memoryUsageHeader"
defaultMessage="Memory Usage"
/>
</EuiFlexItem>
</EuiFlexGroup>
</MlPageHeader>
<SavedObjectsWarning onCloseFlyout={refresh} />
{showNodeInfo ? (
<>
<EuiTabs data-test-subj="mlMemoryUsageTabs">
<EuiTab
isSelected={selectedTab === TAB.NODES}
onClick={() => setSelectedTab(TAB.NODES)}
data-test-subj="mlMemoryUsageTab-nodes"
>
<FormattedMessage id="xpack.ml.memoryUsage.nodesTab" defaultMessage="Nodes" />
</EuiTab>
<EuiTab
isSelected={selectedTab === TAB.MEMORY_USAGE}
onClick={() => setSelectedTab(TAB.MEMORY_USAGE)}
data-test-subj="mlMemoryUsageTab-memory-usage"
>
<FormattedMessage id="xpack.ml.memoryUsage.memoryTab" defaultMessage="Memory usage" />
</EuiTab>
</EuiTabs>
{selectedTab === TAB.NODES ? <NodesList /> : <MemoryPage />}
</>
) : (
<JobMemoryTreeMap />
)}
</>
);
};

View file

@ -0,0 +1,47 @@
/*
* 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 { useEffect, useState } from 'react';
import useMountedState from 'react-use/lib/useMountedState';
import { extractErrorProperties } from '@kbn/ml-error-utils';
import { useRefresh } from '../routing/use_refresh';
import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models';
import type { MlSavedObjectType } from '../../../common/types/saved_objects';
import type { MemoryUsageInfo } from '../../../common/types/trained_models';
export const useMemoryUsage = (node?: string, type?: MlSavedObjectType) => {
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<MemoryUsageInfo[]>([]);
const [error, setError] = useState<string | undefined>();
const refresh = useRefresh();
const trainedModelsApiService = useTrainedModelsApiService();
const isMounted = useMountedState();
useEffect(
function getMemoryData() {
const fetchData = async () => {
setLoading(true);
try {
const resp = await trainedModelsApiService.memoryUsage(type, node);
setError(undefined);
setData(resp);
} catch (e) {
const err = extractErrorProperties(e);
setError(err.message);
}
setLoading(false);
};
if (!isMounted()) return;
fetchData();
},
[node, type, trainedModelsApiService, isMounted, refresh]
);
return { loading, data, error };
};

View file

@ -585,7 +585,7 @@ export const ModelsList: FC<Props> = ({
}
return (
<>
<div data-test-subj="mlTrainedModelsList">
<SpaceManagementContextWrapper>
<SavedObjectsWarning onCloseFlyout={fetchModels} forceRefresh={isLoading} />
<EuiFlexGroup justifyContent="spaceBetween">
@ -744,6 +744,6 @@ export const ModelsList: FC<Props> = ({
/>
) : null}
</SpaceManagementContextWrapper>
</>
</div>
);
};

View file

@ -14,7 +14,11 @@ import {
getAnalysisType,
type DataFrameAnalysisConfigType,
} from '@kbn/ml-data-frame-analytics-utils';
import { useMlLink, useMlLocator, useNavigateToPath } from '../../../contexts/kibana';
import {
useMlLink,
useMlManagementLocatorInternal,
useNavigateToPath,
} from '../../../contexts/kibana';
import type { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { getViewLinkStatus } from '../../../data_frame_analytics/pages/analytics_management/components/action_view/get_view_link_status';
import { ML_PAGES } from '../../../../../common/constants/locator';
@ -59,7 +63,7 @@ export const ViewLink: FC<Props> = ({ item }) => {
};
export function useTableActions(): Array<Action<DataFrameAnalyticsListRow>> {
const locator = useMlLocator();
const mlManagementLocator = useMlManagementLocatorInternal();
const navigateToPath = useNavigateToPath();
return [
@ -74,13 +78,16 @@ export function useTableActions(): Array<Action<DataFrameAnalyticsListRow>> {
type: 'icon',
icon: 'list',
onClick: async (item) => {
const path = await locator?.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
pageState: {
jobId: item.id,
const { url } = await mlManagementLocator?.getUrl(
{
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
pageState: {
jobId: item.id,
},
},
});
await navigateToPath(path);
'analytics'
);
await navigateToPath(url);
},
},
{

View file

@ -22,8 +22,7 @@ import { ML_OVERVIEW_PANELS } from '../../../../../common/types/storage';
import { AnalyticsTable } from './table';
import { useGetAnalytics } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service';
import type { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { useMlLink } from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useMlManagementLocator } from '../../../contexts/kibana';
import { useRefresh } from '../../../routing/use_refresh';
import type { GetDataFrameAnalyticsStatsResponseError } from '../../../services/ml_api_service/data_frame_analytics';
import { AnalyticsEmptyPrompt } from '../../../data_frame_analytics/pages/analytics_management/components/empty_prompt';
@ -35,14 +34,16 @@ interface Props {
}
export const AnalyticsPanel: FC<Props> = ({ setLazyJobCount }) => {
const refresh = useRefresh();
const mlManagementLocator = useMlManagementLocator();
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
const [analyticsStats, setAnalyticsStats] = useState<StatEntry[] | undefined>(undefined);
const [errorMessage, setErrorMessage] = useState<GetDataFrameAnalyticsStatsResponseError>();
const [isInitialized, setIsInitialized] = useState(false);
const manageJobsLink = useMlLink({
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
const manageJobsLink = mlManagementLocator?.useUrl({
sectionId: 'ml',
appId: 'analytics',
});
const [panelsState, setPanelsState] = useStorage<
@ -94,6 +95,7 @@ export const AnalyticsPanel: FC<Props> = ({ setLazyJobCount }) => {
return (
<CollapsiblePanel
dataTestSubj={'mlDataFrameAnalyticsPanel'}
isOpen={panelsState.dfaJobs}
onToggle={(update) => {
setPanelsState({ ...panelsState, dfaJobs: update });

View file

@ -0,0 +1,140 @@
/*
* 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 { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { MlSummaryJobs } from '../../../../common/types/anomaly_detection_jobs';
import { ML_PAGES } from '../../../locator';
import adImage from '../../jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_kibana.png';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../ml_nodes_check';
import { useMlApi, useMlLocator, useMlManagementLocator } from '../../contexts/kibana';
import { AnomalyDetectionEmptyState } from '../../jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state';
import { MLEmptyPromptCard } from '../../components/overview/ml_empty_prompt_card';
export const AnomalyDetectionOverviewCard: FC = () => {
const [canGetJobs, canCreateJob] = usePermissionCheck(['canGetJobs', 'canCreateJob']);
const disableCreateAnomalyDetectionJob = !canCreateJob || !mlNodesAvailable();
const [isLoading, setIsLoading] = useState(false);
const [hasADJobs, setHasADJobs] = useState(false);
const mlApi = useMlApi();
const mlLocator = useMlLocator();
const mlManagementLocator = useMlManagementLocator();
const loadJobs = useCallback(async () => {
setIsLoading(true);
try {
const jobsResult: MlSummaryJobs = await mlApi.jobs.jobsSummary([]);
if (jobsResult?.length > 0) {
setHasADJobs(true);
}
setIsLoading(false);
} catch (e) {
setIsLoading(false);
}
}, [mlApi]);
useEffect(() => {
loadJobs();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const redirectToMultiMetricExplorer = useCallback(async () => {
if (!mlLocator) return;
await mlLocator.navigate({
sectionId: 'ml',
page: ML_PAGES.ANOMALY_EXPLORER,
});
}, [mlLocator]);
const redirectToManageJobs = useCallback(async () => {
if (!mlManagementLocator) return;
await mlManagementLocator.navigate({
sectionId: 'ml',
appId: `anomaly_detection`,
});
}, [mlManagementLocator]);
const showEmptyState = !isLoading && !hasADJobs;
const availableActions = useMemo(() => {
const actions: React.ReactNode[] = [];
if (hasADJobs) {
actions.push(
<EuiButton
color="primary"
fill
onClick={redirectToMultiMetricExplorer}
isDisabled={!canGetJobs}
data-test-subj="multiMetricExplorerButton"
>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.anomalyExplorerButtonText"
defaultMessage="Anomaly explorer"
/>
</EuiButton>
);
}
if (canGetJobs && canCreateJob) {
actions.push(
<EuiButton
color="primary"
onClick={redirectToManageJobs}
isDisabled={disableCreateAnomalyDetectionJob}
data-test-subj="manageJobsButton"
>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.manageJobsButton"
defaultMessage="Manage jobs"
/>
</EuiButton>
);
}
return actions;
}, [
disableCreateAnomalyDetectionJob,
hasADJobs,
canCreateJob,
canGetJobs,
redirectToMultiMetricExplorer,
redirectToManageJobs,
]);
return showEmptyState ? (
<AnomalyDetectionEmptyState />
) : (
<MLEmptyPromptCard
layout="horizontal"
hasBorder={true}
hasShadow={false}
iconSrc={adImage}
iconAlt={i18n.translate('xpack.ml.overview.anomalyDetection.title', {
defaultMessage: 'Anomaly detection',
})}
title={i18n.translate('xpack.ml.overview.anomalyDetection.createFirstJobMessage', {
defaultMessage: 'Spot anomalies faster',
})}
body={
<p>
<FormattedMessage
id="xpack.ml.overview.anomalyDetection.emptyPromptText"
defaultMessage="Start automatically spotting anomalies hiding in your time series data and resolve issues faster."
/>
</p>
}
actions={availableActions}
data-test-subj="mlOverviewAnomalyDetectionCard"
/>
);
};

View file

@ -8,12 +8,17 @@
import { i18n } from '@kbn/i18n';
import type { Action } from '@elastic/eui/src/components/basic_table/action_types';
import { useTimefilter } from '@kbn/ml-date-picker';
import { useMlLocator, useNavigateToPath } from '../../../contexts/kibana';
import {
useMlLocator,
useNavigateToPath,
useMlManagementLocatorInternal,
} from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/locator';
import type { Group } from './anomaly_detection_panel';
export function useGroupActions(): Array<Action<Group>> {
const locator = useMlLocator();
const mlManagementLocator = useMlManagementLocatorInternal();
const timefilter = useTimefilter();
const navigateToPath = useNavigateToPath();
@ -32,13 +37,16 @@ export function useGroupActions(): Array<Action<Group>> {
icon: 'list',
type: 'icon',
onClick: async (item) => {
const path = await locator?.getUrl({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState: {
groupIds: [item.id],
const { url } = await mlManagementLocator?.getUrl(
{
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
pageState: {
groupIds: [item.id],
},
},
});
await navigateToPath(path);
'anomaly_detection'
);
await navigateToPath(url);
},
},
{

View file

@ -14,10 +14,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { useStorage } from '@kbn/ml-local-storage';
import type { MlStorageKey, TMlStorageMapped } from '../../../../../common/types/storage';
import { ML_OVERVIEW_PANELS } from '../../../../../common/types/storage';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { OverviewStatsBar } from '../../../components/collapsible_panel/collapsible_panel';
import { CollapsiblePanel } from '../../../components/collapsible_panel';
import { useMlApi, useMlKibana, useMlLink } from '../../../contexts/kibana';
import { useMlApi, useMlKibana, useMlManagementLocator } from '../../../contexts/kibana';
import { AnomalyDetectionTable } from './table';
import { getGroupsFromJobs, getStatsBarData } from './utils';
import type { Dictionary } from '../../../../../common/types/common';
@ -57,6 +56,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
services: { charts: chartsService },
} = useMlKibana();
const mlApi = useMlApi();
const mlManagementLocator = useMlManagementLocator();
const { displayErrorToast } = useToastNotificationService();
const { showNodeInfo } = useEnabledFeatures();
@ -68,8 +68,9 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
TMlStorageMapped<typeof ML_OVERVIEW_PANELS>
>(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
const manageJobsLink = useMlLink({
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
const manageJobsLink = mlManagementLocator?.useUrl({
sectionId: 'ml',
appId: 'anomaly_detection',
});
const [isLoading, setIsLoading] = useState(false);
@ -181,6 +182,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
return (
<CollapsiblePanel
dataTestSubj={'mlAnomalyDetectionPanel'}
isOpen={panelsState.adJobs}
onToggle={(update) => {
setPanelsState({ ...panelsState, adJobs: update });

View file

@ -0,0 +1,127 @@
/*
* 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 { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ML_PAGES } from '../../../locator';
import dfaImage from '../../data_frame_analytics/pages/analytics_management/components/empty_prompt/data_frame_analytics_kibana.png';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
import { useMlApi, useMlLocator, useMlManagementLocator } from '../../contexts/kibana';
import { mlNodesAvailable } from '../../ml_nodes_check';
import { MLEmptyPromptCard } from '../../components/overview/ml_empty_prompt_card';
import { AnalyticsEmptyPrompt } from '../../data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt';
export const DataFrameAnalyticsOverviewCard: FC = () => {
const mlLocator = useMlLocator();
const mlManagementLocator = useMlManagementLocator();
const [hasDFAs, setHasDFAs] = useState(false);
const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics, canGetDataFrameAnalytics] =
usePermissionCheck([
'canCreateDataFrameAnalytics',
'canStartStopDataFrameAnalytics',
'canGetDataFrameAnalytics',
]);
const disabled =
!mlNodesAvailable() || !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics;
const navigateToResultsExplorer = useCallback(async () => {
if (!mlLocator) return;
await mlLocator.navigate({
sectionId: 'ml',
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
});
}, [mlLocator]);
const navigateToDFAManagementPath = useCallback(async () => {
if (!mlManagementLocator) return;
await mlManagementLocator.navigate({
sectionId: 'ml',
appId: `analytics`,
});
}, [mlManagementLocator]);
const availableActions = useMemo(() => {
const actions: React.ReactNode[] = [];
if (hasDFAs) {
actions.push(
<EuiButton
fill
color="primary"
onClick={navigateToResultsExplorer}
isDisabled={!canGetDataFrameAnalytics}
data-test-subj="mlAnalyticsResultsExplorerButton"
>
<FormattedMessage
id="xpack.ml.overview.dataFrameAnalytics.resultsExplorerButtonText"
defaultMessage="Results explorer"
/>
</EuiButton>
);
}
if (!disabled) {
actions.push(
<EuiButton
color="primary"
onClick={navigateToDFAManagementPath}
isDisabled={disabled}
data-test-subj="mlAnalyticsManageDFAJobsButton"
>
<FormattedMessage
id="xpack.ml.overview.dataFrameAnalytics.manageJobsButton"
defaultMessage="Manage jobs"
/>
</EuiButton>
);
}
return actions;
}, [
disabled,
navigateToDFAManagementPath,
hasDFAs,
navigateToResultsExplorer,
canGetDataFrameAnalytics,
]);
const mlApi = useMlApi();
useEffect(() => {
const fetchAnalytics = async () => {
const analyticsConfigs = await mlApi.dataFrameAnalytics.getDataFrameAnalytics();
if (analyticsConfigs?.count > 0) {
setHasDFAs(true);
}
};
fetchAnalytics();
}, [mlApi]);
return !hasDFAs ? (
<AnalyticsEmptyPrompt />
) : (
<MLEmptyPromptCard
iconSrc={dfaImage}
iconAlt={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', {
defaultMessage: 'Trained analysis of your data',
})}
title={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', {
defaultMessage: 'Trained analysis of your data',
})}
body={
<FormattedMessage
id="xpack.ml.overview.analyticsList.emptyPromptText"
defaultMessage="Train outlier detection, regression, or classification machine learning models using data frame analytics."
/>
}
actions={availableActions}
data-test-subj="mlOverviewDataFrameAnalyticsCard"
/>
);
};

View file

@ -0,0 +1,60 @@
/*
* 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 { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
interface OverviewFooterItemProps {
icon: 'machineLearningApp' | 'documentation' | 'dashboardApp';
title: string;
description: string;
docLink: string;
callToAction: React.ReactNode;
}
export const OverviewFooterItem: FC<OverviewFooterItemProps> = ({
icon,
title,
description,
docLink,
callToAction,
}) => (
<EuiFlexGroup direction="column" gutterSize="xs" alignItems="flexStart">
<EuiFlexItem grow={false}>
<EuiButtonIcon
display="base"
color="primary"
href={docLink}
iconType={icon}
aria-label={i18n.translate('xpack.ml.overviewFooterItem.documentationLink', {
defaultMessage: 'Documentation link',
})}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h3>{title}</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{description}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSpacer size="s" />
{callToAction}
</EuiFlexItem>
</EuiFlexGroup>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -0,0 +1,142 @@
/*
* 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 { FC } from 'react';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { EuiBetaBadge, EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { MLOverviewCard } from './overview_ml_page';
import { ML_PAGES } from '../../locator';
export const DataVisualizerGrid: FC<{ buttonType?: 'empty' | 'full'; isEsqlEnabled: boolean }> = ({
buttonType,
isEsqlEnabled,
}) => (
<EuiFlexGrid gutterSize="m" columns={2}>
{isEsqlEnabled ? (
<MLOverviewCard
layout="horizontal"
path={ML_PAGES.DATA_VISUALIZER_ESQL}
title={
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.selectESQLTitle"
defaultMessage="ES|QL"
/>
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBetaBadge
label=""
iconType="beaker"
size="m"
color="hollow"
tooltipContent={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.esqlTechnicalPreviewBadge.titleMsg"
defaultMessage="ES|QL data visualizer is in technical preview."
/>
}
tooltipPosition={'right'}
/>
</EuiFlexItem>
</EuiFlexGroup>
}
description={i18n.translate(
'xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg',
{
defaultMessage:
'The Elasticsearch Query Language (ES|QL) provides a powerful way to filter, transform, and analyze data stored in Elasticsearch.',
}
)}
iconType="esqlVis"
buttonLabel={i18n.translate('xpack.ml.datavisualizer.selector.tryESQLNowButtonLabel', {
defaultMessage: 'Try it now!',
})}
cardDataTestSubj="mlDataVisualizerSelectESQLCard"
buttonDataTestSubj="mlDataVisualizerSelectESQLButton"
buttonType={buttonType}
/>
) : null}
<MLOverviewCard
layout="horizontal"
path="/filedatavisualizer"
title={i18n.translate('xpack.ml.datavisualizer.selector.importDataTitle', {
defaultMessage: 'Visualize data from a file',
})}
description={i18n.translate('xpack.ml.datavisualizer.selector.importDataDescription', {
defaultMessage:
'Upload your file, analyze its data, and optionally import the data into an index.',
})}
iconType="addDataApp"
buttonLabel={i18n.translate('xpack.ml.datavisualizer.selector.uploadFileButtonLabel', {
defaultMessage: 'Select file',
})}
cardDataTestSubj="mlDataVisualizerCardImportData"
buttonDataTestSubj="mlDataVisualizerUploadFileButton"
buttonType={buttonType}
/>
<MLOverviewCard
layout="horizontal"
path="/datavisualizer_index_select"
title={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewTitle', {
defaultMessage: 'Visualize data from a data view',
})}
description={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewTitle', {
defaultMessage: 'Analyze data, its shape, and statistical metadata from a data view.',
})}
iconType="dataVisualizer"
buttonLabel={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewButtonLabel', {
defaultMessage: 'Select data view',
})}
cardDataTestSubj="mlDataVisualizerCardIndexData"
buttonDataTestSubj="mlDataVisualizerSelectIndexButton"
buttonType={buttonType}
/>
<MLOverviewCard
layout="horizontal"
path="/data_drift_index_select"
title={
<>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.selectDataDriftTitle"
defaultMessage="Visualize data using data drift"
/>{' '}
<EuiBetaBadge
label=""
iconType="beaker"
size="m"
color="hollow"
tooltipContent={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.dataDriftTechnicalPreviewBadge.titleMsg"
defaultMessage="Data drift visualizer is in technical preview."
/>
}
tooltipPosition={'right'}
/>
</>
}
description={i18n.translate('xpack.ml.datavisualizer.selector.dataDriftDescription', {
defaultMessage:
'Detecting data drifts enables you to identify potential performance issues.',
})}
iconType="visTagCloud"
buttonLabel={i18n.translate('xpack.ml.datavisualizer.selector.selectDataViewButtonLabel', {
defaultMessage: 'Compare data distribution',
})}
cardDataTestSubj="mlDataVisualizerCardDataDriftData"
buttonDataTestSubj="mlDataVisualizerSelectDataDriftButton"
buttonType={buttonType}
/>
</EuiFlexGrid>
);

View file

@ -0,0 +1,137 @@
/*
* 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 { FC } from 'react';
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiLink, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import { useStorage } from '@kbn/ml-local-storage';
import { OverviewStatsBar } from '../components/collapsible_panel/collapsible_panel';
import { ML_PAGES } from '../../../common/constants/locator';
import type { MlStorageKey, TMlStorageMapped } from '../../../common/types/storage';
import { ML_OVERVIEW_PANELS } from '../../../common/types/storage';
import { CollapsiblePanel } from '../components/collapsible_panel';
import { usePermissionCheck } from '../capabilities/check_capabilities';
import { mlNodesAvailable } from '../ml_nodes_check';
import { OverviewContent } from './components/content';
import { NodeAvailableWarning } from '../components/node_available_warning';
import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warning';
import { SavedObjectsWarning } from '../components/saved_objects_warning';
import { UpgradeWarning } from '../components/upgrade';
import { HelpMenu } from '../components/help_menu';
import { useMlKibana, useMlLink } from '../contexts/kibana';
import { NodesList } from '../memory_usage/nodes_overview';
import { MlPageHeader } from '../components/page_header';
import { PageTitle } from '../components/page_title';
import { getMlNodesCount } from '../ml_nodes_check/check_ml_nodes';
export const overviewPanelDefaultState = Object.freeze({
nodes: true,
adJobs: true,
dfaJobs: true,
});
export const OverviewPage: FC = () => {
const [canViewMlNodes, canCreateJob] = usePermissionCheck(['canViewMlNodes', 'canCreateJob']);
const disableCreateAnomalyDetectionJob = !canCreateJob || !mlNodesAvailable();
const {
services: { docLinks },
} = useMlKibana();
const helpLink = docLinks.links.ml.guide;
const viewNodesLink = useMlLink({
page: ML_PAGES.MEMORY_USAGE,
});
const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true });
const [adLazyJobCount, setAdLazyJobCount] = useState(0);
const [dfaLazyJobCount, setDfaLazyJobCount] = useState(0);
const [panelsState, setPanelsState] = useStorage<
MlStorageKey,
TMlStorageMapped<typeof ML_OVERVIEW_PANELS>
>(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
return (
<div>
<MlPageHeader>
<PageTitle
title={i18n.translate('xpack.ml.management.machineLearningOverview.overviewLabel', {
defaultMessage: 'Machine Learning Overview',
})}
/>
</MlPageHeader>
<NodeAvailableWarning />
<JobsAwaitingNodeWarning jobCount={adLazyJobCount + dfaLazyJobCount} />
<SavedObjectsWarning
onCloseFlyout={() => {
const { from, to } = timefilter.getTime();
const timeRange = { start: from, end: to };
mlTimefilterRefresh$.next({
lastRefresh: Date.now(),
timeRange,
});
}}
/>
<UpgradeWarning />
{canViewMlNodes ? (
<>
<CollapsiblePanel
isOpen={panelsState.nodes}
onToggle={(update) => {
setPanelsState({ ...panelsState, nodes: update });
}}
header={
<FormattedMessage id="xpack.ml.overview.nodesPanel.header" defaultMessage="Nodes" />
}
headerItems={[
<OverviewStatsBar
inputStats={[
{
label: i18n.translate('xpack.ml.overview.nodesPanel.totalNodesLabel', {
defaultMessage: 'Total',
}),
value: getMlNodesCount(),
'data-test-subj': 'mlTotalNodesCount',
},
]}
dataTestSub={'mlOverviewAnalyticsStatsBar'}
/>,
<EuiLink href={viewNodesLink}>
{i18n.translate('xpack.ml.overview.nodesPanel.viewNodeLink', {
defaultMessage: 'View nodes',
})}
</EuiLink>,
]}
ariaLabel={i18n.translate('xpack.ml.overview.nodesPanel.ariaLabel', {
defaultMessage: 'overview panel',
})}
>
<NodesList compactView />
</CollapsiblePanel>
<EuiSpacer size="m" />
</>
) : null}
<OverviewContent
createAnomalyDetectionJobDisabled={disableCreateAnomalyDetectionJob}
setAdLazyJobCount={setAdLazyJobCount}
setDfaLazyJobCount={setDfaLazyJobCount}
/>
<HelpMenu docLink={helpLink} />
</div>
);
};
// required for dynamic import using React.lazy()
// eslint-disable-next-line import/no-default-export
export default OverviewPage;

View file

@ -0,0 +1,487 @@
/*
* 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 { FC } from 'react';
import React, { useMemo, useState, useEffect } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { GetUserProfileResponse } from '@kbn/core-user-profile-browser';
import type { EuiCardProps } from '@elastic/eui';
import {
EuiButton,
EuiButtonEmpty,
EuiButtonIcon,
EuiCard,
EuiFlexGrid,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiImage,
EuiLink,
EuiPageHeader,
EuiPageBody,
EuiSpacer,
EuiText,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ENABLE_ESQL } from '@kbn/esql-utils';
import { UpgradeWarning } from '../components/upgrade';
import { HelpMenu } from '../components/help_menu';
import { useMlKibana, useNavigateToPath } from '../contexts/kibana';
import { useCreateAndNavigateToManagementMlLink } from '../contexts/kibana/use_create_url';
import { ML_PAGES } from '../../../common/constants/locator';
import { MlPageHeader } from '../components/page_header';
import { AnomalyDetectionOverviewCard } from './components/anomaly_detection_overview';
import { DataFrameAnalyticsOverviewCard } from './components/data_frame_analytics_overview';
import { useEnabledFeatures } from '../contexts/ml';
import { DataVisualizerGrid } from './data_visualizer_grid';
import { OverviewFooterItem } from './components/overview_ml_footer_item';
import bannerImageLight from './components/welcome--light.png';
import bannerImageDark from './components/welcome--dark.png';
export const overviewPanelDefaultState = Object.freeze({
nodes: true,
adJobs: true,
dfaJobs: true,
});
export const MLOverviewCard = ({
layout,
path,
title,
description,
iconType,
buttonLabel,
cardDataTestSubj,
buttonDataTestSubj,
buttonType = 'empty',
}: {
path: string;
iconType: string;
buttonLabel: string;
cardDataTestSubj: string;
buttonDataTestSubj: string;
buttonType: string | undefined;
} & EuiCardProps) => {
const navigateToPath = useNavigateToPath();
const ButtonComponent = buttonType === 'empty' ? EuiButtonEmpty : EuiButton;
return (
<EuiFlexItem data-test-subj={cardDataTestSubj}>
<EuiCard
layout={layout}
data-test-subj={cardDataTestSubj}
hasBorder
icon={
<EuiButtonIcon
display="base"
size="s"
iconType={iconType}
onClick={() => navigateToPath(path)}
aria-labelledby="mlOverviewCardTitle"
/>
}
title={title}
titleSize="s"
titleElement="h3"
id="mlOverviewCardTitle"
>
<EuiFlexItem grow={true}>
<EuiSpacer size="m" />
<EuiText size="s">{description}</EuiText>
</EuiFlexItem>
<EuiSpacer size="m" />
<ButtonComponent
flush="left"
target="_self"
onClick={() => navigateToPath(path)}
data-test-subj={buttonDataTestSubj}
aria-label={buttonLabel}
>
{buttonLabel}
</ButtonComponent>
</EuiCard>
</EuiFlexItem>
);
};
export const OverviewPage: FC = () => {
const [user, setUser] = useState<GetUserProfileResponse | undefined>();
const {
services: { docLinks, uiSettings, userProfile },
} = useMlKibana();
const { colorMode } = useEuiTheme();
const isDarkTheme = colorMode === 'DARK';
const { isADEnabled, isDFAEnabled, isNLPEnabled } = useEnabledFeatures();
const helpLink = docLinks.links.ml.guide;
const trainedModelsDocLink = docLinks.links.ml.trainedModels;
const navigateToPath = useNavigateToPath();
const navigateToTrainedModels = useCreateAndNavigateToManagementMlLink(
'',
ML_PAGES.TRAINED_MODELS_MANAGE
);
const navigateToStackManagementMLOverview = useCreateAndNavigateToManagementMlLink(
'',
'overview'
);
const isEsqlEnabled = useMemo(() => uiSettings.get(ENABLE_ESQL), [uiSettings]);
useEffect(
function loadUserName() {
async function loadUser() {
const currentUser = await userProfile.getCurrent();
setUser(currentUser);
}
loadUser();
},
[userProfile]
);
return (
<>
<MlPageHeader>
<EuiPageHeader
alignItems="center"
restrictWidth={1200}
pageTitle={
<EuiFlexGroup direction="column" gutterSize="s">
{Boolean(user) ? (
<EuiFlexItem grow={false}>
<EuiText color="subdued">
<h4>
{user
? i18n.translate(
'xpack.ml.overview.welcomeBanner.header.greeting.customTitle',
{
defaultMessage: '👋 Hi {name}!',
values: { name: user.user.username ?? '' },
}
)
: i18n.translate(
'xpack.ml.overview.welcomeBanner.header.greeting.defaultTitle',
{
defaultMessage: '👋 Hi',
}
)}
</h4>
</EuiText>
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1>
<FormattedMessage
id="xpack.ml.overview.welcomeBanner.header.title"
defaultMessage="Welcome to the Machine Learning Hub"
/>
</h1>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText color="subdued">
{i18n.translate('xpack.ml.overview.welcomeBanner.header.titleDescription', {
defaultMessage:
'Analyze your data and generate models for its patterns of behavior.',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
}
rightSideItems={[
<EuiImage
alt={i18n.translate('xpack.ml.overview.welcomeBanner.header.imageAlt', {
defaultMessage: 'Welcome to the Machine Learning Hub',
})}
src={isDarkTheme ? bannerImageDark : bannerImageLight}
size="l"
/>,
]}
/>
</MlPageHeader>
<EuiPageBody restrictWidth={1200}>
<UpgradeWarning />
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="m" direction="column">
{isADEnabled || isDFAEnabled ? (
<>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiTitle size="s">
<h2>
{i18n.translate('xpack.ml.overview.analyzeYourDataTitle', {
defaultMessage: 'Analyze your data',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="m">
{isADEnabled ? (
<EuiFlexItem data-test-subj="mlOverviewAnomalyDetectionCard">
<AnomalyDetectionOverviewCard />
</EuiFlexItem>
) : null}
{isDFAEnabled ? (
<EuiFlexItem data-test-subj="mlOverviewCardDataFrameAnalytics">
<DataFrameAnalyticsOverviewCard />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
</>
) : null}
<EuiFlexGroup direction="column">
<EuiTitle size="s">
<h2>
{i18n.translate('xpack.ml.overview.aiopsLabsTitle', {
defaultMessage: 'AIOps Labs',
})}
</h2>
</EuiTitle>
<EuiFlexGrid gutterSize="m" columns={3}>
<EuiFlexItem>
<EuiCard
textAlign="left"
layout="vertical"
hasBorder
icon={
<EuiButtonIcon
display="base"
size="s"
onClick={() => navigateToPath('/aiops/log_rate_analysis_index_select')}
iconType="logRateAnalysis"
aria-label={i18n.translate('xpack.ml.overview.logRateAnalysis.title', {
defaultMessage: 'Log Rate Analysis',
})}
/>
}
title={
<FormattedMessage
id="xpack.ml.overview.logRateAnalysis.title"
defaultMessage="Log Rate Analysis"
/>
}
titleElement="h3"
titleSize="s"
description={
<>
<FormattedMessage
id="xpack.ml.overview.logRateAnalysis.description"
defaultMessage="Advanced statistical methods to identify reasons for increases or decreases in log rates and displays the statistically significant data in a tabular format."
/>
</>
}
footer={
<EuiButton
color="primary"
target="_self"
onClick={() => navigateToPath('/aiops/log_rate_analysis_index_select')}
data-test-subj="mlOverviewCardLogRateAnalysisButton"
>
<FormattedMessage
id="xpack.ml.overview.logRateAnalysis.startAnalysisButton"
defaultMessage="Start analysis"
/>
</EuiButton>
}
data-test-subj="mlOverviewCardLogRateAnalysis"
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCard
textAlign="left"
layout="vertical"
hasBorder
icon={
<EuiButtonIcon
display="base"
size="s"
iconType="logPatternAnalysis"
onClick={() => navigateToPath('/aiops/log_categorization_index_select')}
aria-label={i18n.translate('xpack.ml.overview.logPatternAnalysisTitle', {
defaultMessage: 'Log Pattern Analysis',
})}
/>
}
title={
<FormattedMessage
id="xpack.ml.overview.logPatternAnalysisTitle"
defaultMessage="Log Pattern Analysis"
/>
}
titleElement="h3"
titleSize="s"
description={
<>
<FormattedMessage
id="xpack.ml.overview.logPatternAnalysisDescription"
defaultMessage="Find patterns in unstructured log messages and make it easier to examine your data."
/>
</>
}
footer={
<EuiButton
color="primary"
target="_self"
onClick={() => navigateToPath('/aiops/log_categorization_index_select')}
data-test-subj="mlOverviewCardLogPatternAnalysisButton"
>
<FormattedMessage
id="xpack.ml.overview.logPatternAnalysis.startAnalysisButton"
defaultMessage="Start analysis"
/>
</EuiButton>
}
data-test-subj="mlOverviewCardLogPatternAnalysis"
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCard
textAlign="left"
layout="vertical"
hasBorder
icon={
<EuiButtonIcon
display="base"
size="s"
iconType="changePointDetection"
onClick={() => navigateToPath('/aiops/change_point_detection_index_select')}
aria-label={i18n.translate('xpack.ml.overview.changePointDetection.title', {
defaultMessage: 'Change Point Detection',
})}
/>
}
title={
<FormattedMessage
id="xpack.ml.overview.changePointDetection.title"
defaultMessage="Change Point Detection"
/>
}
titleElement="h3"
titleSize="s"
description={
<>
<FormattedMessage
id="xpack.ml.overview.changePointDetection.description"
defaultMessage="Change point detection uses the change point aggregation to detect distribution changes, trend changes, and other statistically significant change points in a metric of your time series data."
/>
</>
}
footer={
<EuiButton
color="primary"
target="_self"
onClick={() => navigateToPath('/aiops/change_point_detection_index_select')}
data-test-subj="mlOverviewCardChangePointDetectionButton"
aria-label={i18n.translate(
'xpack.ml.overview.changePointDetection.startDetectionButton',
{
defaultMessage: 'Start detection',
}
)}
>
<FormattedMessage
id="xpack.ml.overview.changePointDetection.startDetectionButton"
defaultMessage="Start detection"
/>
</EuiButton>
}
data-test-subj="mlOverviewCardChangePointDetection"
/>
</EuiFlexItem>
</EuiFlexGrid>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup direction="column">
<EuiTitle size="s">
<h2>
{i18n.translate('xpack.ml.overview.visualizeYourDataTitle', {
defaultMessage: 'Visualize your data',
})}
</h2>
</EuiTitle>
<DataVisualizerGrid isEsqlEnabled={isEsqlEnabled} />
</EuiFlexGroup>
</EuiFlexGroup>
<HelpMenu docLink={helpLink} />
</EuiPageBody>
<EuiHorizontalRule />
<EuiFlexGroup>
{isADEnabled || isNLPEnabled || isDFAEnabled ? (
<EuiFlexItem>
<OverviewFooterItem
icon="dashboardApp"
title={i18n.translate('xpack.ml.overview.manageMlAssetsTitle', {
defaultMessage: 'Manage ML Assets',
})}
description={i18n.translate('xpack.ml.overview.manageMlAssetsDescription', {
defaultMessage: 'Overview of your ML jobs, memory usage, and notifications.',
})}
docLink={helpLink}
callToAction={
<EuiLink onClick={navigateToStackManagementMLOverview}>
{i18n.translate('xpack.ml.overview.goToManagmentLink', {
defaultMessage: 'Go to Management',
})}
</EuiLink>
}
/>
</EuiFlexItem>
) : null}
{isNLPEnabled || isDFAEnabled ? (
<EuiFlexItem>
<OverviewFooterItem
icon="machineLearningApp"
title={i18n.translate('xpack.ml.overview.trainedModelsTitle', {
defaultMessage: 'Trained Models',
})}
description={i18n.translate('xpack.ml.overview.trainedModelsDescription', {
defaultMessage:
'Add or manage Trained Models. See deployment stats or add a new deployment.',
})}
docLink={trainedModelsDocLink}
callToAction={
<EuiLink onClick={navigateToTrainedModels}>
{i18n.translate('xpack.ml.overview.manageTrainedModelsLink', {
defaultMessage: 'Manage Trained Models',
})}
</EuiLink>
}
/>
</EuiFlexItem>
) : null}
<EuiFlexItem>
<OverviewFooterItem
icon="documentation"
title={i18n.translate('xpack.ml.overview.browseDocumentationTitle', {
defaultMessage: 'Browse documentation',
})}
description={i18n.translate('xpack.ml.overview.browseDocumentationDescription', {
defaultMessage: 'In-depth guides on Elastic Machine Learning.',
})}
docLink={helpLink}
callToAction={
<EuiLink href={helpLink} external target="_blank">
{i18n.translate('xpack.ml.overview.startReadingDocsLink', {
defaultMessage: 'Start Reading',
})}
</EuiLink>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};
// required for dynamic import using React.lazy()
// eslint-disable-next-line import/no-default-export
export default OverviewPage;

View file

@ -6,16 +6,18 @@
*/
import type { FC } from 'react';
import React, { useState } from 'react';
import React, { useMemo, useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiLink, EuiSpacer } from '@elastic/eui';
import { EuiSpacer, EuiTab, EuiTabs, EuiNotificationBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import type { TimefilterContract } from '@kbn/data-plugin/public';
import { mlTimefilterRefresh$ } from '@kbn/ml-date-picker';
import { useStorage } from '@kbn/ml-local-storage';
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { useUrlState } from '@kbn/ml-url-state';
import { OverviewStatsBar } from '../components/collapsible_panel/collapsible_panel';
import { ML_PAGES } from '../../../common/constants/locator';
import type { MlStorageKey, TMlStorageMapped } from '../../../common/types/storage';
import { ML_OVERVIEW_PANELS } from '../../../common/types/storage';
import { ML_OVERVIEW_PANELS, ML_OVERVIEW_PANELS_EXTENDED } from '../../../common/types/storage';
import { CollapsiblePanel } from '../components/collapsible_panel';
import { usePermissionCheck } from '../capabilities/check_capabilities';
import { mlNodesAvailable } from '../ml_nodes_check';
@ -25,19 +27,49 @@ import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warnin
import { SavedObjectsWarning } from '../components/saved_objects_warning';
import { UpgradeWarning } from '../components/upgrade';
import { HelpMenu } from '../components/help_menu';
import { useMlKibana, useMlLink } from '../contexts/kibana';
import { useMlKibana } from '../contexts/kibana';
import { useMlNotifications } from '../contexts/ml/ml_notifications_context';
import { NodesList } from '../memory_usage/nodes_overview';
import { MlPageHeader } from '../components/page_header';
import { PageTitle } from '../components/page_title';
import { getMlNodesCount } from '../ml_nodes_check/check_ml_nodes';
import { MemoryPage } from '../memory_usage/memory_tree_map/memory_page';
import { NotificationsList } from '../notifications/components/notifications_list';
import { useMemoryUsage } from '../memory_usage/use_memory_usage';
import { useFieldFormatter } from '../contexts/kibana';
import type { MlSavedObjectType } from '../../../common/types/saved_objects';
import { type StatEntry } from '../components/collapsible_panel/collapsible_panel';
export const overviewPanelDefaultState = Object.freeze({
nodes: true,
adJobs: true,
dfaJobs: true,
});
const overviewPanelExtendedDefaultState = Object.freeze({
memoryUsage: true,
});
const MEMORY_STATS_LABELS = {
'anomaly-detector': i18n.translate('xpack.ml.overview.memoryUsagePanel.anomalyDetectionLabel', {
defaultMessage: 'Anomaly detection',
}),
'data-frame-analytics': i18n.translate(
'xpack.ml.overview.memoryUsagePanel.dataFrameAnalyticsLabel',
{
defaultMessage: 'Data frame analytics',
}
),
'trained-model': i18n.translate('xpack.ml.overview.memoryUsagePanel.trainedModelsLabel', {
defaultMessage: 'Trained models',
}),
};
export const OverviewPage: FC = () => {
enum TAB_IDS {
OVERVIEW = 'overview',
NOTIFICATIONS = 'notifications',
}
export type TabIdType = (typeof TAB_IDS)[keyof typeof TAB_IDS];
export const OverviewPage: FC<{ timefilter: TimefilterContract }> = ({ timefilter }) => {
const [canViewMlNodes, canCreateJob] = usePermissionCheck(['canViewMlNodes', 'canCreateJob']);
const disableCreateAnomalyDetectionJob = !canCreateJob || !mlNodesAvailable();
@ -45,27 +77,201 @@ export const OverviewPage: FC = () => {
services: { docLinks },
} = useMlKibana();
const helpLink = docLinks.links.ml.guide;
const {
data: memoryUsageData,
error: memoryUsageError,
loading: memoryUsageLoading,
} = useMemoryUsage();
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
const { notificationsCounts } = useMlNotifications();
const errorsAndWarningCount =
(notificationsCounts?.error ?? 0) + (notificationsCounts?.warning ?? 0);
const [pageState, setPageState] = useUrlState('_g');
const viewNodesLink = useMlLink({
page: ML_PAGES.MEMORY_USAGE,
});
const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true });
const selectedTabId = pageState?.tab ?? TAB_IDS.OVERVIEW;
const setSelectedTabId = (tabId: TabIdType) => {
setPageState({ tab: tabId });
};
const [adLazyJobCount, setAdLazyJobCount] = useState(0);
const [dfaLazyJobCount, setDfaLazyJobCount] = useState(0);
const [memoryUsageStats, setMemoryUsageStats] = useState<StatEntry[]>([]);
const [panelsState, setPanelsState] = useStorage<
MlStorageKey,
TMlStorageMapped<typeof ML_OVERVIEW_PANELS>
>(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
const [panelsExtendedState, setPanelsExtendedState] = useStorage<
MlStorageKey,
TMlStorageMapped<typeof ML_OVERVIEW_PANELS_EXTENDED>
>(ML_OVERVIEW_PANELS_EXTENDED, overviewPanelExtendedDefaultState);
useEffect(
function setUpMemoryUsageStats() {
if (memoryUsageLoading || memoryUsageError) return;
const sumSizeByType = memoryUsageData.reduce((acc, current) => {
const { type, size } = current;
if (acc[type] === undefined) {
acc[type] = size;
} else {
acc[type] = (acc[type] as number) + size;
}
return acc;
}, {} as Record<MlSavedObjectType, number>);
const formattedSizes: StatEntry[] = [];
Object.keys(sumSizeByType).forEach((type) => {
const size = sumSizeByType[type as MlSavedObjectType];
formattedSizes.push({
label: MEMORY_STATS_LABELS[type as MlSavedObjectType],
value: bytesFormatter(size),
'data-test-subj': `mlMemoryUsageStatsCount ${type}`,
});
});
setMemoryUsageStats(formattedSizes);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[memoryUsageLoading, memoryUsageError]
);
const tabs = useMemo(
() => [
{
id: TAB_IDS.OVERVIEW,
name: (
<FormattedMessage id="xpack.ml.overview.overviewTabLabel" defaultMessage="Overview" />
),
content: (
<>
<>
<CollapsiblePanel
dataTestSubj="mlMemoryUsagePanel"
isOpen={panelsExtendedState.memoryUsage}
onToggle={(update) => {
setPanelsExtendedState({ ...panelsExtendedState, memoryUsage: update });
}}
header={
<FormattedMessage
id="xpack.ml.overview.memoryUsagePanel.header"
defaultMessage="Memory Usage"
/>
}
headerItems={[
<OverviewStatsBar
inputStats={memoryUsageStats}
dataTestSub={'mlOverviewMemoryUsageStatsBar'}
/>,
]}
ariaLabel={i18n.translate('xpack.ml.overview.memoryUsagePanel.ariaLabel', {
defaultMessage: 'Memory usage panel',
})}
>
<MemoryPage />
</CollapsiblePanel>
<EuiSpacer size="m" />
</>
{canViewMlNodes ? (
<>
<CollapsiblePanel
dataTestSubj="mlNodesPanel"
isOpen={panelsState.nodes}
onToggle={(update) => {
setPanelsState({ ...panelsState, nodes: update });
}}
header={
<FormattedMessage
id="xpack.ml.overview.nodesPanel.header"
defaultMessage="Nodes"
/>
}
headerItems={[
<OverviewStatsBar
inputStats={[
{
label: i18n.translate('xpack.ml.overview.nodesPanel.totalNodesLabel', {
defaultMessage: 'Total',
}),
value: getMlNodesCount(),
'data-test-subj': 'mlTotalNodesCount',
},
]}
dataTestSub={'mlOverviewAnalyticsStatsBar'}
/>,
]}
ariaLabel={i18n.translate('xpack.ml.overview.nodesPanel.ariaLabel', {
defaultMessage: 'overview panel',
})}
>
<NodesList />
</CollapsiblePanel>
<EuiSpacer size="m" />
</>
) : null}
<OverviewContent
createAnomalyDetectionJobDisabled={disableCreateAnomalyDetectionJob}
setAdLazyJobCount={setAdLazyJobCount}
setDfaLazyJobCount={setDfaLazyJobCount}
/>
</>
),
},
{
id: TAB_IDS.NOTIFICATIONS,
name: (
<FormattedMessage
id="xpack.ml.overview.notificationsTabLabel"
defaultMessage="Notifications"
/>
),
append: errorsAndWarningCount ? (
<EuiNotificationBadge
aria-label={i18n.translate('xpack.ml.overview.notificationsIndicator.unreadErrors', {
defaultMessage: 'Unread errors or warnings indicator.',
})}
data-test-subj={'mlNotificationErrorsIndicator'}
>
{errorsAndWarningCount}
</EuiNotificationBadge>
) : undefined,
content: <NotificationsList />,
},
],
[
canViewMlNodes,
disableCreateAnomalyDetectionJob,
errorsAndWarningCount,
memoryUsageStats,
panelsState,
panelsExtendedState,
setPanelsState,
setPanelsExtendedState,
]
);
const renderTabs = () => {
return tabs.map((tab) => (
<EuiTab
key={tab.id}
onClick={() => setSelectedTabId(tab.id)}
isSelected={tab.id === selectedTabId}
data-test-subj={`mlManagementOverviewPageTabs ${tab.id}`}
append={tab.append}
>
{tab.name}
</EuiTab>
));
};
return (
<div>
<div data-test-subj="mlStackManagementOverviewPage">
<MlPageHeader>
<PageTitle
title={i18n.translate('xpack.ml.overview.overviewLabel', {
defaultMessage: 'Overview',
title={i18n.translate('xpack.ml.management.machineLearningOverview.overviewLabel', {
defaultMessage: 'Machine Learning Overview',
})}
/>
</MlPageHeader>
@ -82,51 +288,9 @@ export const OverviewPage: FC = () => {
}}
/>
<UpgradeWarning />
{canViewMlNodes ? (
<>
<CollapsiblePanel
isOpen={panelsState.nodes}
onToggle={(update) => {
setPanelsState({ ...panelsState, nodes: update });
}}
header={
<FormattedMessage id="xpack.ml.overview.nodesPanel.header" defaultMessage="Nodes" />
}
headerItems={[
<OverviewStatsBar
inputStats={[
{
label: i18n.translate('xpack.ml.overview.nodesPanel.totalNodesLabel', {
defaultMessage: 'Total',
}),
value: getMlNodesCount(),
'data-test-subj': 'mlTotalNodesCount',
},
]}
dataTestSub={'mlOverviewAnalyticsStatsBar'}
/>,
<EuiLink href={viewNodesLink}>
{i18n.translate('xpack.ml.overview.nodesPanel.viewNodeLink', {
defaultMessage: 'View nodes',
})}
</EuiLink>,
]}
ariaLabel={i18n.translate('xpack.ml.overview.nodesPanel.ariaLabel', {
defaultMessage: 'overview panel',
})}
>
<NodesList compactView />
</CollapsiblePanel>
<EuiSpacer size="m" />
</>
) : null}
<OverviewContent
createAnomalyDetectionJobDisabled={disableCreateAnomalyDetectionJob}
setAdLazyJobCount={setAdLazyJobCount}
setDfaLazyJobCount={setDfaLazyJobCount}
/>
<EuiTabs>{renderTabs()}</EuiTabs>
<EuiSpacer />
{tabs.find((tab) => tab.id === selectedTabId)?.content}
<HelpMenu docLink={helpLink} />
</div>
);

View file

@ -7,9 +7,103 @@
import { i18n } from '@kbn/i18n';
import type { ChromeBreadcrumb } from '@kbn/core/public';
import type { ChromeBreadcrumb, CoreStart } from '@kbn/core/public';
import type { NavigateToPath } from '../contexts/kibana';
import { ML_PAGES } from '../../../common/constants/locator';
export type NavigateToApp = CoreStart['application']['navigateToApp'];
type ManagementBreadcrumbType = ChromeBreadcrumb & {
appId?: string;
path?: string;
};
const stackManagementBreadcrumbText = i18n.translate(
'xpack.ml.settings.breadcrumbs.stackManagementLabel',
{
defaultMessage: 'Stack Management',
}
);
export const ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.anomalyDetectionManagementBreadcrumbLabel', {
defaultMessage: 'Anomaly Detection Jobs',
}),
appId: 'anomaly_detection',
path: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
deepLinkId: 'ml:anomalyDetection',
};
export const CREATE_JOB_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.createJobManagementBreadcrumbLabel', {
defaultMessage: 'Create job',
}),
path: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB,
appId: `anomaly_detection`,
};
export const DATA_FRAME_ANALYTICS_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.dataFrameAnalyticsManagementLabel', {
defaultMessage: 'Data Frame Analytics Jobs',
}),
appId: 'analytics',
path: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
deepLinkId: 'ml:dataFrameAnalytics',
};
export const TRAINED_MODELS_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.trainedModelManagementLabel', {
defaultMessage: 'Trained Models',
}),
appId: 'trained_models',
path: '',
deepLinkId: 'management:trained_models',
};
export const SUPPLIED_CONFIGURATIONS_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.suppliedConfigurationsManagementLabel', {
defaultMessage: 'Supplied configurations',
}),
appId: `anomaly_detection`,
path: 'ad_supplied_configurations',
deepLinkId: 'ml:suppliedConfigurations',
};
export const SETTINGS_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.settingsBreadcrumbLabel', {
defaultMessage: 'Anomaly Detection Settings',
}),
appId: 'ad_settings',
path: '',
deepLinkId: 'ml:settings',
};
export const FILTER_LISTS_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterListsManagementLabel', {
defaultMessage: 'Filter lists',
}),
appId: 'ad_settings',
path: ML_PAGES.FILTER_LISTS_MANAGE,
deepLinkId: 'ml:filterListsSettings',
};
export const CALENDAR_LISTS_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarListManagementLabel', {
defaultMessage: 'Calendar management',
}),
appId: 'ad_settings',
path: ML_PAGES.CALENDARS_MANAGE,
deepLinkId: 'ml:calendarSettings',
};
export const CALENDAR_DST_LISTS_MANAGEMENT_BREADCRUMB: ManagementBreadcrumbType = {
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarListManagementLabel', {
defaultMessage: 'Calendar DST management',
}),
appId: 'ad_settings',
path: ML_PAGES.CALENDARS_DST_MANAGE,
deepLinkId: 'ml:calendarSettings',
};
export const ML_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.machineLearningBreadcrumbLabel', {
@ -18,46 +112,6 @@ export const ML_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
href: '/',
});
export const SETTINGS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settingsBreadcrumbLabel', {
defaultMessage: 'Settings',
}),
href: '/settings',
deepLinkId: 'ml:settings',
});
export const ANOMALY_DETECTION_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.anomalyDetectionBreadcrumbLabel', {
defaultMessage: 'Anomaly Detection',
}),
href: '/jobs',
deepLinkId: 'ml:anomalyDetection',
});
export const DATA_FRAME_ANALYTICS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.dataFrameAnalyticsLabel', {
defaultMessage: 'Data Frame Analytics',
}),
href: '/data_frame_analytics',
deepLinkId: 'ml:dataFrameAnalytics',
});
export const TRAINED_MODELS: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.modelManagementLabel', {
defaultMessage: 'Model Management',
}),
href: '/trained_models',
deepLinkId: 'ml:modelManagement',
});
export const SUPPLIED_CONFIGURATIONS: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.suppliedConfigurationsLabel', {
defaultMessage: 'Supplied configurations',
}),
href: '/supplied_configurations',
deepLinkId: 'ml:suppliedConfigurations',
});
export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.datavisualizerBreadcrumbLabel', {
defaultMessage: 'Data Visualizer',
@ -113,52 +167,38 @@ export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({
deepLinkId: 'ml:changePointDetections',
});
export const CREATE_JOB_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.createJobsBreadcrumbLabel', {
defaultMessage: 'Create job',
}),
href: '/jobs/new_job',
});
export const CALENDAR_MANAGEMENT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagementLabel', {
defaultMessage: 'Calendar management',
}),
href: '/settings/calendars_list',
deepLinkId: 'ml:calendarSettings',
});
export const CALENDAR_DST_MANAGEMENT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagementLabel', {
defaultMessage: 'Calendar DST management',
}),
href: '/settings/calendars_dst_list',
deepLinkId: 'ml:calendarSettings',
});
export const FILTER_LISTS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterListsLabel', {
defaultMessage: 'Filter lists',
}),
href: '/settings/filter_lists',
deepLinkId: 'ml:filterListsSettings',
});
export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settings.breadcrumbs.dataComparisonLabel', {
defaultMessage: 'Data drift',
defaultMessage: 'Data Drift',
}),
href: '/data_drift_index_select',
deepLinkId: 'ml:dataDrift',
});
export const DATA_DRIFT_INDEX_SELECT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settings.breadcrumbs.dataComparisonLabel', {
defaultMessage: 'Select Data View',
}),
href: '/data_drift_index_select',
deepLinkId: 'ml:dataDrift',
});
const managementBreadcrumbs = {
ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB,
CALENDAR_DST_LISTS_MANAGEMENT_BREADCRUMB,
CALENDAR_LISTS_MANAGEMENT_BREADCRUMB,
CREATE_JOB_MANAGEMENT_BREADCRUMB,
DATA_FRAME_ANALYTICS_MANAGEMENT_BREADCRUMB,
FILTER_LISTS_MANAGEMENT_BREADCRUMB,
SUPPLIED_CONFIGURATIONS_MANAGEMENT_BREADCRUMB,
SETTINGS_MANAGEMENT_BREADCRUMB,
TRAINED_MODELS_MANAGEMENT_BREADCRUMB,
};
type ManagementBreadcrumb = keyof typeof managementBreadcrumbs;
const breadcrumbs = {
ML_BREADCRUMB,
SETTINGS_BREADCRUMB,
ANOMALY_DETECTION_BREADCRUMB,
DATA_FRAME_ANALYTICS_BREADCRUMB,
TRAINED_MODELS,
DATA_DRIFT_BREADCRUMB,
DATA_DRIFT_INDEX_SELECT_BREADCRUMB,
DATA_VISUALIZER_BREADCRUMB,
AIOPS_BREADCRUMB_LOG_RATE_ANALYSIS,
AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS,
@ -166,11 +206,6 @@ const breadcrumbs = {
LOG_RATE_ANALYSIS,
LOG_PATTERN_ANALYSIS,
CHANGE_POINT_DETECTION,
CREATE_JOB_BREADCRUMB,
CALENDAR_MANAGEMENT_BREADCRUMB,
CALENDAR_DST_MANAGEMENT_BREADCRUMB,
FILTER_LISTS_BREADCRUMB,
SUPPLIED_CONFIGURATIONS,
};
type Breadcrumb = keyof typeof breadcrumbs;
@ -200,3 +235,36 @@ export const getBreadcrumbWithUrlForApp = (
: {}),
};
};
export const getStackManagementBreadcrumb = (navigateToApp: NavigateToApp): ChromeBreadcrumb => {
return {
text: stackManagementBreadcrumbText,
onClick: (e) => {
e.preventDefault();
navigateToApp('management');
},
};
};
export const getMlManagementBreadcrumb = (
breadcrumbName: ManagementBreadcrumb,
navigateToApp: NavigateToApp
): ChromeBreadcrumb => {
const { appId, deepLinkId, path, text } = managementBreadcrumbs[breadcrumbName];
return {
text,
onClick: (e) => {
e.preventDefault();
navigateToApp('management', { path: `/ml/${appId}/${path}` });
},
...(deepLinkId ? { deepLinkId } : {}),
};
};
export const getADSettingsBreadcrumbs = (navigateToApp: NavigateToApp) => {
return [
getStackManagementBreadcrumb(navigateToApp),
getMlManagementBreadcrumb('ANOMALY_DETECTION_MANAGEMENT_BREADCRUMB', navigateToApp),
getMlManagementBreadcrumb('SETTINGS_MANAGEMENT_BREADCRUMB', navigateToApp),
];
};

View file

@ -56,7 +56,7 @@ export interface PageProps {
export interface PageDependencies {
history: AppMountParameters['history'];
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
setHeaderActionMenu?: AppMountParameters['setHeaderActionMenu'];
setBreadcrumbs: ChromeStart['setBreadcrumbs'];
}
@ -85,11 +85,12 @@ export const PageLoader: FC<PropsWithChildren<{ context: RouteResolverContext }>
*/
export const MlRouter: FC<{
pageDeps: PageDependencies;
}> = ({ pageDeps }) => (
entryPoint?: string; // update this to finite set of possible ids - now 'jobs' - maybe make part of the context to avoid prop drilling?
}> = ({ pageDeps, entryPoint }) => (
<Router history={pageDeps.history}>
<UrlStateProvider>
<MlNotificationsContextProvider>
<MlPage pageDeps={pageDeps} />
<MlPage pageDeps={pageDeps} entryPoint={entryPoint} />
</MlNotificationsContextProvider>
</UrlStateProvider>
</Router>

Some files were not shown because too many files have changed in this diff Show more