[OneDiscover] Remove logs explorer (#209565)

Resolves #209261

## Summary

Removes the code used to render Logs Explorer. This does not result in
any functional changes.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Thom Heymann 2025-02-06 17:40:55 +00:00 committed by GitHub
parent 2b5bbf8f86
commit 151ce184c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
201 changed files with 37 additions and 11932 deletions

1
.github/CODEOWNERS vendored
View file

@ -937,7 +937,6 @@ x-pack/solutions/observability/plugins/inventory @elastic/obs-ux-infra_services-
x-pack/solutions/observability/plugins/inventory/e2e @elastic/obs-ux-infra_services-team
x-pack/solutions/observability/plugins/investigate @elastic/obs-ux-management-team
x-pack/solutions/observability/plugins/investigate_app @elastic/obs-ux-management-team
x-pack/solutions/observability/plugins/logs_explorer @elastic/obs-ux-logs-team
x-pack/solutions/observability/plugins/metrics_data_access @elastic/obs-ux-infra_services-team
x-pack/solutions/observability/plugins/observability @elastic/obs-ux-management-team
x-pack/solutions/observability/plugins/observability_ai_assistant_app @elastic/obs-ai-assistant

View file

@ -629,7 +629,6 @@
"@kbn/logging": "link:src/platform/packages/shared/kbn-logging",
"@kbn/logging-mocks": "link:src/platform/packages/shared/kbn-logging-mocks",
"@kbn/logs-data-access-plugin": "link:x-pack/platform/plugins/shared/logs_data_access",
"@kbn/logs-explorer-plugin": "link:x-pack/solutions/observability/plugins/logs_explorer",
"@kbn/logs-overview": "link:x-pack/platform/packages/shared/logs_overview",
"@kbn/logs-shared-plugin": "link:x-pack/platform/plugins/shared/logs_shared",
"@kbn/logstash-plugin": "link:x-pack/platform/plugins/private/logstash",

View file

@ -99,7 +99,6 @@ pageLoadAssetSize:
links: 8000
lists: 22900
logsDataAccess: 16759
logsExplorer: 60000
logsShared: 281060
logstash: 53548
management: 46112

View file

@ -54,7 +54,6 @@ export const storybookAliases = {
investigate: 'x-pack/solutions/observability/plugins/investigate_app/.storybook',
kibana_react: 'src/platform/plugins/shared/kibana_react/.storybook',
lists: 'x-pack/solutions/security/plugins/lists/.storybook',
logs_explorer: 'x-pack/solutions/observability/plugins/logs_explorer/.storybook',
management: 'packages/kbn-management/storybook/config',
observability: 'x-pack/solutions/observability/plugins/observability/.storybook',
observability_ai_assistant:

View file

@ -359,7 +359,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.observability.unsafe.alertDetails.observability.enabled (boolean?)',
'xpack.observability.unsafe.thresholdRule.enabled (boolean?)',
'xpack.observability_onboarding.ui.enabled (boolean?)',
'xpack.observabilityLogsExplorer.navigation.showAppLink (boolean?|never)',
'xpack.observabilityAIAssistant.scope (observability?|search?)',
'xpack.observabilityAiAssistantManagement.logSourcesEnabled (boolean?)',
'xpack.observabilityAiAssistantManagement.spacesEnabled (boolean?)',

View file

@ -1188,8 +1188,6 @@
"@kbn/logging-mocks/*": ["src/platform/packages/shared/kbn-logging-mocks/*"],
"@kbn/logs-data-access-plugin": ["x-pack/platform/plugins/shared/logs_data_access"],
"@kbn/logs-data-access-plugin/*": ["x-pack/platform/plugins/shared/logs_data_access/*"],
"@kbn/logs-explorer-plugin": ["x-pack/solutions/observability/plugins/logs_explorer"],
"@kbn/logs-explorer-plugin/*": ["x-pack/solutions/observability/plugins/logs_explorer/*"],
"@kbn/logs-overview": ["x-pack/platform/packages/shared/logs_overview"],
"@kbn/logs-overview/*": ["x-pack/platform/packages/shared/logs_overview/*"],
"@kbn/logs-shared-plugin": ["x-pack/platform/plugins/shared/logs_shared"],
@ -2096,7 +2094,9 @@
"@kbn/zod-helpers/*": ["src/platform/packages/shared/kbn-zod-helpers/*"],
// END AUTOMATED PACKAGE LISTING
// Allows for importing from `kibana` package for the exported types.
"@emotion/core": ["typings/@emotion"]
"@emotion/core": [
"typings/@emotion"
]
},
// Support .tsx files and transform JSX into calls to React.createElement
"jsx": "react",
@ -2170,4 +2170,4 @@
"@kbn/ambient-storybook-types"
]
}
}
}

View file

@ -62,7 +62,6 @@
"xpack.inferenceEndpointUICommon": "platform/packages/shared/kbn-inference-endpoint-ui-common",
"xpack.infra": "solutions/observability/plugins/infra",
"xpack.logsDataAccess": "platform/plugins/shared/logs_data_access",
"xpack.logsExplorer": "solutions/observability/plugins/logs_explorer",
"xpack.logsShared": "platform/plugins/shared/logs_shared",
"xpack.fleet": "platform/plugins/shared/fleet",
"xpack.ingestPipelines": "platform/plugins/shared/ingest_pipelines",
@ -188,4 +187,4 @@
"@kbn/translations-plugin/translations/ja-JP.json",
"@kbn/translations-plugin/translations/fr-FR.json"
]
}
}

View file

@ -24642,49 +24642,6 @@
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingLabel": "Paramètre avancé des sources de logs",
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingLinkText": "page des paramètres avancés",
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingTitle": "Sources de log",
"xpack.logsExplorer.dataSourceSelection.createAdHocDataViewFailedToastMessage": "La section par défaut a été modifiée en \"All log datasets\" (Tous les logs des ensembles de données)",
"xpack.logsExplorer.dataSourceSelection.createAdHocDataViewFailedToastTitle": "Nous n'avons pas pu créer de vue des données pour votre sélection.",
"xpack.logsExplorer.dataSourceSelection.restoreDatasetSelectionFailedToastMessage": "La section par défaut a été modifiée en \"All log datasets\" (Tous les logs des ensembles de données)",
"xpack.logsExplorer.dataSourceSelection.restoreDatasetSelectionFailedToastTitle": "Nous n'avons pu restaurer votre sélection d'ensembles de données.",
"xpack.logsExplorer.dataSourceSelection.restoreDataViewSelectionFailedToastMessage": "La section par défaut a été modifiée en \"All log datasets\" (Tous les logs des ensembles de données)",
"xpack.logsExplorer.dataSourceSelection.restoreDataViewSelectionFailedToastTitle": "Nous n'avons pu restaurer votre affichage d'ensembles de données.",
"xpack.logsExplorer.dataSourceSelector.addDataLabel": "Ajouter des données",
"xpack.logsExplorer.dataSourceSelector.dataViewCount": "{count, plural, one {# vue de données} other {# vues de données}}",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.allDataViewTypes": "Afficher tout",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.logsDataViewType": "Logs uniquement",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.selectDataViewType": "Sélectionner un type",
"xpack.logsExplorer.dataSourceSelector.dataViews": "Vues de données",
"xpack.logsExplorer.dataSourceSelector.error": "erreur",
"xpack.logsExplorer.dataSourceSelector.integrations": "Intégrations",
"xpack.logsExplorer.dataSourceSelector.noDataError": "Une {error} s'est produite lors de l'obtention de vos données. Veuillez réessayer.",
"xpack.logsExplorer.dataSourceSelector.noDataRetry": "Réessayer",
"xpack.logsExplorer.dataSourceSelector.noDatasets": "Flux de données introuvable",
"xpack.logsExplorer.dataSourceSelector.noDatasetsDescription": "Aucun ensemble de données ou résultat de recherche trouvé.",
"xpack.logsExplorer.dataSourceSelector.noDataViews": "Aucune vue de données trouvée",
"xpack.logsExplorer.dataSourceSelector.noDataViewsDescription": "Aucune vue de données ou résultat de recherche trouvé.",
"xpack.logsExplorer.dataSourceSelector.noIntegrations": "Aucune intégration trouvée",
"xpack.logsExplorer.dataSourceSelector.noIntegrationsDescription": "Aucune intégration ou résultat de recherche trouvé.",
"xpack.logsExplorer.dataSourceSelector.openDiscover": "S'ouvre dans Discover",
"xpack.logsExplorer.dataSourceSelector.showAllLogs": "Afficher tous les logs",
"xpack.logsExplorer.dataSourceSelector.sortOrders": "Sens de tri",
"xpack.logsExplorer.dataSourceSelector.TryEsql": "Langue : ES|QL",
"xpack.logsExplorer.dataSourceSelector.uncategorized": "Non catégorisé",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "Affiche le {logLevel} du document et les champs {message}.",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "Lorsque le champ de message est vide, l'une des informations suivantes s'affiche :",
"xpack.logsExplorer.dataTable.header.popover.content": "Contenu",
"xpack.logsExplorer.dataTable.header.popover.resource": "Ressource",
"xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "Les champs fournissant des informations sur la source du document, comme :",
"xpack.logsExplorer.docViews.logsOverview.title": "Aperçu",
"xpack.logsExplorer.flyoutDetail.title": "Détails du log",
"xpack.logsExplorer.flyoutDetail.value.hover.filterFor": "Filtrer sur cette {value}",
"xpack.logsExplorer.flyoutDetail.value.hover.filterOut": "Exclure cette {value}",
"xpack.logsExplorer.popoverAction.closePopover": "Fermer la fenêtre contextuelle",
"xpack.logsExplorer.popoverAction.copyValue": "Copier la valeur",
"xpack.logsExplorer.popoverAction.copyValueAriaText": "Copier la valeur de {fieldName}",
"xpack.logsExplorer.popoverAction.filterFor": "Filtrer sur",
"xpack.logsExplorer.popoverAction.filterOut": "Exclure",
"xpack.logsExplorer.popoverAction.openPopover": "Ouvrir la fenêtre contextuelle",
"xpack.logsExplorer.TechPreview": "Version d'évaluation technique",
"xpack.logsShared.dataSearch.abortedRequestErrorMessage": "La demande a été annulée.",
"xpack.logsShared.dataSearch.cancelButtonLabel": "Annuler la demande",
"xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "Réessayer",
@ -31636,22 +31593,7 @@
"xpack.observabilityAiAssistantManagement.settingsPage.simulatedFunctionCallingLabel": "Simuler un appel de fonction",
"xpack.observabilityAiAssistantManagement.settingsTab.h3.searchConnectorIndexPatternLabel": "Modèle d'indexation de connecteur de recherche",
"xpack.observabilityAiAssistantManagement.span.expandRowLabel": "Développer la ligne",
"xpack.observabilityLogsExplorer.alertsPopover.buttonLabel": "Alertes",
"xpack.observabilityLogsExplorer.alertsPopover.createRuleMenuItem": "Créer une règle",
"xpack.observabilityLogsExplorer.alertsPopover.createSLOMenuItem": "Créer un SLO",
"xpack.observabilityLogsExplorer.alertsPopover.manageRulesMenuItem": "{canCreateRule, select, true{Gérer} other{Afficher}} les règles",
"xpack.observabilityLogsExplorer.appTitle": "Explorateur de logs",
"xpack.observabilityLogsExplorer.createSlo": "Créer un SLO",
"xpack.observabilityLogsExplorer.datasetQualityLinkTitle": "Ensembles de données",
"xpack.observabilityLogsExplorer.discoverLinkTitle": "Ouvrir dans Discover",
"xpack.observabilityLogsExplorer.feedbackLinkTitle": "Donner un retour",
"xpack.observabilityLogsExplorer.feedbackToast.buttonText": "Répondre à une enquête rapide",
"xpack.observabilityLogsExplorer.feedbackToast.text": "Partagez avec nous votre expérience d'intégration pour nous aider à l'améliorer.",
"xpack.observabilityLogsExplorer.feedbackToast.title": "Dites-nous ce que vous pensez !",
"xpack.observabilityLogsExplorer.InitializingTitle": "Initialisation de l'explorateur de logs",
"xpack.observabilityLogsExplorer.logsAppTitle": "Logs",
"xpack.observabilityLogsExplorer.observabilityAppTitle": "Observabilité",
"xpack.observabilityLogsExplorer.onboardingLinkTitle": "Ajouter des données",
"xpack.observabilityLogsOverview.categorizedLogsFilterLabel": "Entrées de log catégorisées",
"xpack.observabilityLogsOverview.discoverLinkTitle": "Ouvrir dans Discover",
"xpack.observabilityLogsOverview.errorTitle": "Erreur",
@ -46878,4 +46820,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "Ce champ est requis.",
"xpack.watcher.watcherDescription": "Détectez les modifications survenant dans vos données en créant, gérant et monitorant des alertes."
}
}
}

View file

@ -24508,49 +24508,6 @@
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingLabel": "ログソース詳細設定",
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingLinkText": "詳細設定ページ",
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingTitle": "ログソース",
"xpack.logsExplorer.dataSourceSelection.createAdHocDataViewFailedToastMessage": "\"すべてのログデータセット\"をデフォルトの選択として切り替えました。",
"xpack.logsExplorer.dataSourceSelection.createAdHocDataViewFailedToastTitle": "選択されたデータビューを作成できませんでした。",
"xpack.logsExplorer.dataSourceSelection.restoreDatasetSelectionFailedToastMessage": "\"すべてのログデータセット\"をデフォルトの選択として切り替えました。",
"xpack.logsExplorer.dataSourceSelection.restoreDatasetSelectionFailedToastTitle": "データセットの選択を復元できませんでした。",
"xpack.logsExplorer.dataSourceSelection.restoreDataViewSelectionFailedToastMessage": "\"すべてのログデータセット\"をデフォルトの選択として切り替えました。",
"xpack.logsExplorer.dataSourceSelection.restoreDataViewSelectionFailedToastTitle": "データビューの選択を復元できませんでした。",
"xpack.logsExplorer.dataSourceSelector.addDataLabel": "データの追加",
"xpack.logsExplorer.dataSourceSelector.dataViewCount": "{count, plural, other {#個のデータビュー}}",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.allDataViewTypes": "すべて表示",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.logsDataViewType": "ログのみ",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.selectDataViewType": "タイプを選択してください",
"xpack.logsExplorer.dataSourceSelector.dataViews": "データビュー",
"xpack.logsExplorer.dataSourceSelector.error": "エラー",
"xpack.logsExplorer.dataSourceSelector.integrations": "統合",
"xpack.logsExplorer.dataSourceSelector.noDataError": "データの取得中に{error}が発生しました。再試行してください。",
"xpack.logsExplorer.dataSourceSelector.noDataRetry": "再試行",
"xpack.logsExplorer.dataSourceSelector.noDatasets": "データストリームが見つかりません",
"xpack.logsExplorer.dataSourceSelector.noDatasetsDescription": "データセットまたは検索結果が見つかりません。",
"xpack.logsExplorer.dataSourceSelector.noDataViews": "データビューが見つかりません",
"xpack.logsExplorer.dataSourceSelector.noDataViewsDescription": "データビューまたは検索結果が見つかりません。",
"xpack.logsExplorer.dataSourceSelector.noIntegrations": "統合が見つかりません",
"xpack.logsExplorer.dataSourceSelector.noIntegrationsDescription": "統合または検索結果が見つかりません。",
"xpack.logsExplorer.dataSourceSelector.openDiscover": "Discoverで開く",
"xpack.logsExplorer.dataSourceSelector.showAllLogs": "すべてのログを表示",
"xpack.logsExplorer.dataSourceSelector.sortOrders": "並べ替え方向",
"xpack.logsExplorer.dataSourceSelector.TryEsql": "言語ES|QL",
"xpack.logsExplorer.dataSourceSelector.uncategorized": "未分類",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "ドキュメントの{logLevel}と{message}フィールドを表示します。",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "メッセージフィールドが空のときには、次のいずれかが表示されます。",
"xpack.logsExplorer.dataTable.header.popover.content": "コンテンツ",
"xpack.logsExplorer.dataTable.header.popover.resource": "リソース",
"xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "次のようなドキュメントのソースに関する情報を提供するフィールド:",
"xpack.logsExplorer.docViews.logsOverview.title": "概要",
"xpack.logsExplorer.flyoutDetail.title": "ログの詳細",
"xpack.logsExplorer.flyoutDetail.value.hover.filterFor": "この{value}でフィルターを適用",
"xpack.logsExplorer.flyoutDetail.value.hover.filterOut": "この{value}を除外",
"xpack.logsExplorer.popoverAction.closePopover": "ポップオーバーを閉じる",
"xpack.logsExplorer.popoverAction.copyValue": "値をコピー",
"xpack.logsExplorer.popoverAction.copyValueAriaText": "{fieldName}の値をコピー",
"xpack.logsExplorer.popoverAction.filterFor": "フィルター",
"xpack.logsExplorer.popoverAction.filterOut": "除外",
"xpack.logsExplorer.popoverAction.openPopover": "ポップオーバーを開く",
"xpack.logsExplorer.TechPreview": "テクニカルプレビュー",
"xpack.logsShared.dataSearch.abortedRequestErrorMessage": "リクエストが中断されましたか。",
"xpack.logsShared.dataSearch.cancelButtonLabel": "リクエストのキャンセル",
"xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "再試行",
@ -31501,22 +31458,7 @@
"xpack.observabilityAiAssistantManagement.settingsPage.simulatedFunctionCallingLabel": "関数呼び出しをシミュレート",
"xpack.observabilityAiAssistantManagement.settingsTab.h3.searchConnectorIndexPatternLabel": "検索コネクターインデックスパターン",
"xpack.observabilityAiAssistantManagement.span.expandRowLabel": "行を展開",
"xpack.observabilityLogsExplorer.alertsPopover.buttonLabel": "アラート",
"xpack.observabilityLogsExplorer.alertsPopover.createRuleMenuItem": "ルールを作成",
"xpack.observabilityLogsExplorer.alertsPopover.createSLOMenuItem": "SLOの作成",
"xpack.observabilityLogsExplorer.alertsPopover.manageRulesMenuItem": "ルールを{canCreateRule, select, true{管理} other{表示}}",
"xpack.observabilityLogsExplorer.appTitle": "ログエクスプローラー",
"xpack.observabilityLogsExplorer.createSlo": "SLOの作成",
"xpack.observabilityLogsExplorer.datasetQualityLinkTitle": "データセット",
"xpack.observabilityLogsExplorer.discoverLinkTitle": "Discoverで開く",
"xpack.observabilityLogsExplorer.feedbackLinkTitle": "フィードバックを作成する",
"xpack.observabilityLogsExplorer.feedbackToast.buttonText": "簡単なアンケートに答える",
"xpack.observabilityLogsExplorer.feedbackToast.text": "オンボーディングの経験を共有し、改善にご協力ください。",
"xpack.observabilityLogsExplorer.feedbackToast.title": "ご意見をお聞かせください。",
"xpack.observabilityLogsExplorer.InitializingTitle": "ログエクスプローラーを初期化しています",
"xpack.observabilityLogsExplorer.logsAppTitle": "ログ",
"xpack.observabilityLogsExplorer.observabilityAppTitle": "Observability",
"xpack.observabilityLogsExplorer.onboardingLinkTitle": "データの追加",
"xpack.observabilityLogsOverview.categorizedLogsFilterLabel": "分類されたログエントリ",
"xpack.observabilityLogsOverview.discoverLinkTitle": "Discoverで開く",
"xpack.observabilityLogsOverview.errorTitle": "エラー",
@ -46729,4 +46671,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。",
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
}
}
}

View file

@ -24100,49 +24100,6 @@
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingLabel": "日志源高级设置",
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingLinkText": "高级设置页面",
"xpack.logsDataAccess.logSourcesSettingInfo.logSourcesSettingTitle": "日志源",
"xpack.logsExplorer.dataSourceSelection.createAdHocDataViewFailedToastMessage": "我们已切换到'所有日志数据集',将其作为默认选择。",
"xpack.logsExplorer.dataSourceSelection.createAdHocDataViewFailedToastTitle": "无法为您选择的内容创建数据视图。",
"xpack.logsExplorer.dataSourceSelection.restoreDatasetSelectionFailedToastMessage": "我们已切换到'所有日志数据集',将其作为默认选择。",
"xpack.logsExplorer.dataSourceSelection.restoreDatasetSelectionFailedToastTitle": "无法还原数据集选择。",
"xpack.logsExplorer.dataSourceSelection.restoreDataViewSelectionFailedToastMessage": "我们已切换到'所有日志数据集',将其作为默认选择。",
"xpack.logsExplorer.dataSourceSelection.restoreDataViewSelectionFailedToastTitle": "无法还原数据视图选择。",
"xpack.logsExplorer.dataSourceSelector.addDataLabel": "添加数据",
"xpack.logsExplorer.dataSourceSelector.dataViewCount": "{count, plural, other {# 个数据视图}}",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.allDataViewTypes": "全部显示",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.logsDataViewType": "仅日志",
"xpack.logsExplorer.dataSourceSelector.dataViewFilter.selectDataViewType": "选择类型",
"xpack.logsExplorer.dataSourceSelector.dataViews": "数据视图",
"xpack.logsExplorer.dataSourceSelector.error": "错误",
"xpack.logsExplorer.dataSourceSelector.integrations": "集成",
"xpack.logsExplorer.dataSourceSelector.noDataError": "获取数据时出现{error}。请重试。",
"xpack.logsExplorer.dataSourceSelector.noDataRetry": "重试",
"xpack.logsExplorer.dataSourceSelector.noDatasets": "找不到任何数据流",
"xpack.logsExplorer.dataSourceSelector.noDatasetsDescription": "找不到数据集或搜索结果。",
"xpack.logsExplorer.dataSourceSelector.noDataViews": "找不到数据视图",
"xpack.logsExplorer.dataSourceSelector.noDataViewsDescription": "找不到数据视图或搜索结果。",
"xpack.logsExplorer.dataSourceSelector.noIntegrations": "未找到集成",
"xpack.logsExplorer.dataSourceSelector.noIntegrationsDescription": "找不到集成或搜索结果。",
"xpack.logsExplorer.dataSourceSelector.openDiscover": "在 Discover 中打开",
"xpack.logsExplorer.dataSourceSelector.showAllLogs": "显示所有日志",
"xpack.logsExplorer.dataSourceSelector.sortOrders": "排序方向",
"xpack.logsExplorer.dataSourceSelector.TryEsql": "语言ES|QL",
"xpack.logsExplorer.dataSourceSelector.uncategorized": "未分类",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "显示该文档的 {logLevel} 和 {message} 字段。",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "消息字段为空时,将显示以下项之一:",
"xpack.logsExplorer.dataTable.header.popover.content": "内容",
"xpack.logsExplorer.dataTable.header.popover.resource": "资源",
"xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "提供有关文档来源信息的字段,例如:",
"xpack.logsExplorer.docViews.logsOverview.title": "概览",
"xpack.logsExplorer.flyoutDetail.title": "日志详情",
"xpack.logsExplorer.flyoutDetail.value.hover.filterFor": "筛留此 {value}",
"xpack.logsExplorer.flyoutDetail.value.hover.filterOut": "筛除此 {value}",
"xpack.logsExplorer.popoverAction.closePopover": "关闭弹出框",
"xpack.logsExplorer.popoverAction.copyValue": "复制值",
"xpack.logsExplorer.popoverAction.copyValueAriaText": "复制 {fieldName} 的值",
"xpack.logsExplorer.popoverAction.filterFor": "筛留",
"xpack.logsExplorer.popoverAction.filterOut": "筛除",
"xpack.logsExplorer.popoverAction.openPopover": "打开弹出框",
"xpack.logsExplorer.TechPreview": "技术预览",
"xpack.logsShared.dataSearch.abortedRequestErrorMessage": "请求已中止。",
"xpack.logsShared.dataSearch.cancelButtonLabel": "取消请求",
"xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "重试",
@ -31047,22 +31004,7 @@
"xpack.observabilityAiAssistantManagement.settingsPage.simulatedFunctionCallingLabel": "模拟函数调用",
"xpack.observabilityAiAssistantManagement.settingsTab.h3.searchConnectorIndexPatternLabel": "搜索连接器索引模式",
"xpack.observabilityAiAssistantManagement.span.expandRowLabel": "展开行",
"xpack.observabilityLogsExplorer.alertsPopover.buttonLabel": "告警",
"xpack.observabilityLogsExplorer.alertsPopover.createRuleMenuItem": "创建规则",
"xpack.observabilityLogsExplorer.alertsPopover.createSLOMenuItem": "创建 SLO",
"xpack.observabilityLogsExplorer.alertsPopover.manageRulesMenuItem": "{canCreateRule, select, true{管理} other{查看}}规则",
"xpack.observabilityLogsExplorer.appTitle": "日志浏览器",
"xpack.observabilityLogsExplorer.createSlo": "创建 SLO",
"xpack.observabilityLogsExplorer.datasetQualityLinkTitle": "数据集",
"xpack.observabilityLogsExplorer.discoverLinkTitle": "在 Discover 中打开",
"xpack.observabilityLogsExplorer.feedbackLinkTitle": "反馈",
"xpack.observabilityLogsExplorer.feedbackToast.buttonText": "参加快速调查",
"xpack.observabilityLogsExplorer.feedbackToast.text": "与我们分享您的载入体验并帮助我们做出改进。",
"xpack.observabilityLogsExplorer.feedbackToast.title": "告诉我们您的看法!",
"xpack.observabilityLogsExplorer.InitializingTitle": "正在初始化日志浏览器",
"xpack.observabilityLogsExplorer.logsAppTitle": "日志",
"xpack.observabilityLogsExplorer.observabilityAppTitle": "Observability",
"xpack.observabilityLogsExplorer.onboardingLinkTitle": "添加数据",
"xpack.observabilityLogsOverview.categorizedLogsFilterLabel": "已归类日志条目",
"xpack.observabilityLogsOverview.discoverLinkTitle": "在 Discover 中打开",
"xpack.observabilityLogsOverview.errorTitle": "错误",
@ -46032,4 +45974,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。",
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
}
}
}

View file

@ -1,11 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiIcon } from '@elastic/eui';
// Export mock package icon that doesn't trigger http requests
export const PackageIcon = () => <EuiIcon type="package" style={{ marginRight: 8 }} />;

View file

@ -1,22 +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.
*/
const defaultConfig = require('@kbn/storybook').defaultConfig;
module.exports = {
...defaultConfig,
stories: ['../**/*.stories.mdx', ...defaultConfig.stories],
webpackFinal: async (config) => {
const originalConfig = await defaultConfig.webpackFinal(config);
// Mock fleet plugin for PackageIcon component
originalConfig.resolve.alias['@kbn/fleet-plugin/public'] = require.resolve(
'./__mocks__/package_icon'
);
return originalConfig;
},
};

View file

@ -1,13 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const parameters = {
docs: {
source: {
type: 'code', // without this, stories in mdx documents freeze the browser
},
},
};

View file

@ -1,66 +0,0 @@
# Logs Explorer
This plugin is home to the `<LogsExplorer />` component and related types. It implements several of the underlying concepts that the [Observability Logs Explorer app](../observability_solution/observability_logs_explorer) builds upon.
## Developing the `<LogsExplorer />` component
⚠ The Logs Explorer is in early stages of development, so the following partly describes the current situation and partly the intended future scenario.
### Dependencies
The goal is for this component to be easy to consume by other Kibana plugins. This means that we should avoid introducing any dependency on plugins that might be consumers. If a situation arises, that would close a cycle in the directed dependency graph, we can take one of two approaches:
- If the newly depended-upon code is not bound to the Kibana plugin life-cycle, it can be factored out into a package that both plugins depend on.
- If it requires integration with the Kibana plugin life-cycle we can invert the direction of the dependency edge by offering a registration API.
We also want to make this plugin available for consumption in deployments without any observability apps. Any observability-specific concepts should therefore be implemented in the `observability_logs_explorer` plugin instead.
While not fully realized yet, the dependency graph would roughly resemble the following:
```mermaid
flowchart TD
obs_logs_explorer_app(Observability Logs Explorer app)
obs_apps(Other Observability apps)
obs_logs_explorer_component(Observability Logs Explorer component)
other_apps(Other non-Observability apps)
logs_explorer_component(Logs Explorer component)
platform(Kibana Platform)
discover(Discover Main container)
fleet(Fleet / EPM)
obs_logs_explorer_app --> obs_logs_explorer_component
obs_apps --> obs_logs_explorer_component
obs_logs_explorer_component --> logs_explorer_component
other_apps --> logs_explorer_component
logs_explorer_component --> discover
logs_explorer_component --> platform
logs_explorer_component --> fleet
discover --> platform
fleet --> platform
```
### API
When designing the API we face two conflicting goals:
- It should be easy to consume by any non-observability app. This means...
- its API needs to be relatively stable and straightforward.
- it should not perform any page-wide changes that could interfere with consuming app's page state (such as URL changes).
- It should be extensible so the Observability Logs Explorer can build on top of this.
In its current state the `<LogsExplorer />` achieves neither goal. To resolve the tension in the future we could export two variants with different sets of properties.
### Principles
**State drives the UI**: When the state and side-effects are scattered throughout the react element hierarchy using hooks, it becomes difficult to reason about the overall application state and the order in which transitions and side-effects take place. To avoid that we're aiming for an approach in which a hierarchy of statecharts is started, that encode state, transitions, and side-effects.
**Minimize custom styling**: EUI is a great library of React components that cover 99% of the use cases. For the 1%, in which an EUI component doesn't quite fit the needs, we should try to enhance EUI instead of overriding its styles locally.
## Resources
- [Statecharts overview](https://statecharts.dev/)
- [Statecharts: A Visual Formalism For Complex Systems (original paper)](https://www.wisdom.weizmann.ac.il/~harel/papers/Statecharts.pdf)
- [XState documentation](https://stately.ai/docs/xstate)
- [XState VS Code extension](https://stately.ai/docs/tools/xstate-vscode-extension)
- [`infra` plugin's state machine patterns](../infra/docs/state_machines/README.md)

View file

@ -1,71 +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 { fieldConstants } from '@kbn/discover-utils';
import { SmartFieldGridColumnOptions } from './display_options';
export * from '@kbn/discover-utils/src/field_constants';
export const LOGS_EXPLORER_PROFILE_ID = 'logs-explorer';
// Virtual column fields
export const CONTENT_FIELD = 'content';
export const RESOURCE_FIELD = 'resource';
// Sizing
export const DATA_GRID_COLUMN_WIDTH_SMALL = 240;
export const DATA_GRID_COLUMN_WIDTH_MEDIUM = 320;
export const ACTIONS_COLUMN_WIDTH = 80;
export const RESOURCE_FIELD_CONFIGURATION: SmartFieldGridColumnOptions = {
type: 'smart-field',
smartField: RESOURCE_FIELD,
fallbackFields: [fieldConstants.HOST_NAME_FIELD, fieldConstants.SERVICE_NAME_FIELD],
width: DATA_GRID_COLUMN_WIDTH_MEDIUM,
};
export const CONTENT_FIELD_CONFIGURATION: SmartFieldGridColumnOptions = {
type: 'smart-field',
smartField: CONTENT_FIELD,
fallbackFields: [fieldConstants.MESSAGE_FIELD],
};
export const SMART_FALLBACK_FIELDS = {
[CONTENT_FIELD]: CONTENT_FIELD_CONFIGURATION,
[RESOURCE_FIELD]: RESOURCE_FIELD_CONFIGURATION,
};
// UI preferences
export const DEFAULT_COLUMNS = [
/**
* We are switching from these virtual columns to the One Discover Summary column.
* In this effort we don't want to immediately cleanup everything about these virtual columns,
* but only disable their instantiation.
* We'll clean this part as soon as we decide to definitevely discard these columns.
**/
// RESOURCE_FIELD_CONFIGURATION,
// CONTENT_FIELD_CONFIGURATION
];
export const DEFAULT_ROW_COUNT = 1;
export const DEFAULT_ROWS_PER_PAGE = 100;
// List of prefixes which needs to be filtered out for Display in Content Column
export const FILTER_OUT_FIELDS_PREFIXES_FOR_CONTENT = [
'_', // Filter fields like '_id', '_score'
'@timestamp',
'agent.',
'elastic_agent.',
'data_stream.',
'ecs.',
'host.',
'container.',
'cloud.',
'kubernetes.',
'orchestrator.',
'log.',
'service.',
];

View file

@ -1,27 +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 { ControlPanels } from './types';
export const availableControlsPanels = {
NAMESPACE: 'data_stream.namespace',
} as const;
export type AvailableControlPanels = typeof availableControlsPanels;
export const controlPanelConfigs: ControlPanels = {
[availableControlsPanels.NAMESPACE]: {
order: 0,
width: 'medium',
grow: false,
type: 'optionsListControl',
fieldName: availableControlsPanels.NAMESPACE,
title: 'Namespace',
},
};
export const availableControlPanelFields = Object.values(availableControlsPanels);

View file

@ -1,9 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './available_control_panels';
export * from './types';

View file

@ -1,28 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
const PanelRT = rt.intersection([
rt.type({
order: rt.number,
type: rt.string,
}),
rt.partial({
width: rt.union([rt.literal('medium'), rt.literal('small'), rt.literal('large')]),
grow: rt.boolean,
dataViewId: rt.string,
fieldName: rt.string,
exclude: rt.boolean,
existsSelected: rt.boolean,
title: rt.union([rt.string, rt.undefined]),
selectedOptions: rt.array(rt.string),
}),
]);
export const ControlPanelRT = rt.record(rt.string, PanelRT);
export type ControlPanels = rt.TypeOf<typeof ControlPanelRT>;

View file

@ -1,44 +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 { Dataset } from '../datasets';
import { DataSourceSelectionStrategy } from './types';
const SELECTION_TYPE = 'all' as const;
export class AllDatasetSelection implements DataSourceSelectionStrategy {
selectionType: typeof SELECTION_TYPE;
selection: {
dataset: Dataset;
};
private constructor({ indices }: { indices: string }) {
this.selectionType = SELECTION_TYPE;
this.selection = {
dataset: Dataset.createAllLogsDataset({ indices }),
};
}
toDataviewSpec() {
return this.selection.dataset.toDataviewSpec();
}
toPlainSelection() {
return {
selectionType: this.selectionType,
};
}
public static getLocatorPlainSelection() {
return {
selectionType: SELECTION_TYPE,
};
}
public static create({ indices }: { indices: string }) {
return new AllDatasetSelection({ indices });
}
}

View file

@ -1,47 +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 { DataViewDescriptor } from '../data_views/models/data_view_descriptor';
import { DataSourceSelectionStrategy, DataViewSelectionPayload } from './types';
export class DataViewSelection implements DataSourceSelectionStrategy {
selectionType: 'dataView';
selection: {
dataView: DataViewDescriptor;
};
private constructor(dataViewDescriptor: DataViewDescriptor) {
this.selectionType = 'dataView';
this.selection = {
dataView: dataViewDescriptor,
};
}
toDataviewSpec() {
return this.selection.dataView.toDataviewSpec();
}
toPlainSelection() {
return {
selectionType: this.selectionType,
selection: {
dataView: this.selection.dataView.toPlain(),
},
};
}
public static fromSelection(selection: DataViewSelectionPayload) {
const { dataView } = selection;
const dataViewDescriptor = DataViewDescriptor.create(dataView);
return DataViewSelection.create(dataViewDescriptor);
}
public static create(dataViewDescriptor: DataViewDescriptor) {
return new DataViewSelection(dataViewDescriptor);
}
}

View file

@ -1,27 +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 { AllDatasetSelection } from './all_dataset_selection';
import { DataViewSelection } from './data_view_selection';
import { SingleDatasetSelection } from './single_dataset_selection';
import { DataSourceSelectionPlain } from './types';
import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';
export const hydrateDataSourceSelection = (
dataSourceSelection: DataSourceSelectionPlain,
allSelection: AllDatasetSelection
) => {
if (dataSourceSelection.selectionType === 'all') {
return allSelection;
} else if (dataSourceSelection.selectionType === 'single') {
return SingleDatasetSelection.fromSelection(dataSourceSelection.selection);
} else if (dataSourceSelection.selectionType === 'dataView') {
return DataViewSelection.fromSelection(dataSourceSelection.selection);
} else {
return UnresolvedDatasetSelection.fromSelection(dataSourceSelection.selection);
}
};

View file

@ -1,55 +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 { AllDatasetSelection } from './all_dataset_selection';
import { DataViewSelection } from './data_view_selection';
import { SingleDatasetSelection } from './single_dataset_selection';
import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';
export type DatasetSelection =
| AllDatasetSelection
| SingleDatasetSelection
| UnresolvedDatasetSelection;
export type DataSourceSelection = DatasetSelection | DataViewSelection;
export type DataSourceSelectionChangeHandler = (selection: DataSourceSelection) => void;
export const isAllDatasetSelection = (input: any): input is AllDatasetSelection => {
return input instanceof AllDatasetSelection;
};
export const isSingleDatasetSelection = (input: any): input is SingleDatasetSelection => {
return input instanceof SingleDatasetSelection;
};
export const isUnresolvedDatasetSelection = (input: any): input is UnresolvedDatasetSelection => {
return input instanceof UnresolvedDatasetSelection;
};
export const isDataViewSelection = (input: any): input is DataViewSelection => {
return input instanceof DataViewSelection;
};
export const isDatasetSelection = (input: any): input is DatasetSelection => {
return (
isAllDatasetSelection(input) ||
isSingleDatasetSelection(input) ||
isUnresolvedDatasetSelection(input)
);
};
export const isDataSourceSelection = (input: any): input is DataSourceSelection => {
return isDatasetSelection(input) || isDataViewSelection(input);
};
export * from './all_dataset_selection';
export * from './data_view_selection';
export * from './hydrate_data_source_selection';
export * from './single_dataset_selection';
export * from './types';
export * from './unresolved_dataset_selection';

View file

@ -1,59 +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 { Dataset } from '../datasets';
import { DataSourceSelectionStrategy, SingleDatasetSelectionPayload } from './types';
export class SingleDatasetSelection implements DataSourceSelectionStrategy {
selectionType: 'single';
selection: {
name?: string;
title?: string;
version?: string;
dataset: Dataset;
};
private constructor(dataset: Dataset) {
this.selectionType = 'single';
this.selection = {
name: dataset.parentIntegration?.name,
title: dataset.parentIntegration?.title,
version: dataset.parentIntegration?.version,
dataset,
};
}
toDataviewSpec() {
return this.selection.dataset.toDataviewSpec();
}
toPlainSelection() {
return {
selectionType: this.selectionType,
selection: {
name: this.selection.name,
title: this.selection.title,
version: this.selection.version,
dataset: this.selection.dataset.toPlain(),
},
};
}
public static fromSelection(selection: SingleDatasetSelectionPayload) {
const { name, title, version, dataset } = selection;
// Attempt reconstructing the integration object
const integration = name && version ? { name, title, version } : undefined;
const datasetInstance = Dataset.create(dataset, integration);
return SingleDatasetSelection.create(datasetInstance);
}
public static create(dataset: Dataset) {
return new SingleDatasetSelection(dataset);
}
}

View file

@ -1,85 +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 { DataViewSpec } from '@kbn/data-views-plugin/common';
import * as rt from 'io-ts';
import { datasetRT } from '../datasets';
import { dataViewDescriptorRT } from '../data_views/types';
const integrationNameRT = rt.partial({
name: rt.string,
});
const integrationTitleRT = rt.partial({
title: rt.string,
});
const integrationVersionRT = rt.partial({
version: rt.string,
});
const singleDatasetSelectionPayloadRT = rt.intersection([
integrationNameRT,
integrationTitleRT,
integrationVersionRT,
rt.type({
dataset: datasetRT,
}),
]);
const dataViewSelectionPayloadRT = rt.type({
dataView: dataViewDescriptorRT,
});
const unresolvedDatasetSelectionPayloadRT = rt.intersection([
integrationNameRT,
rt.type({
dataset: datasetRT,
}),
]);
export const allDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('all'),
});
export const dataViewSelectionPlainRT = rt.type({
selectionType: rt.literal('dataView'),
selection: dataViewSelectionPayloadRT,
});
export const singleDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('single'),
selection: singleDatasetSelectionPayloadRT,
});
export const unresolvedDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('unresolved'),
selection: unresolvedDatasetSelectionPayloadRT,
});
export const dataSourceSelectionPlainRT = rt.union([
allDatasetSelectionPlainRT,
dataViewSelectionPlainRT,
singleDatasetSelectionPlainRT,
unresolvedDatasetSelectionPlainRT,
]);
export type SingleDatasetSelectionPayload = rt.TypeOf<typeof singleDatasetSelectionPayloadRT>;
export type DataViewSelectionPayload = rt.TypeOf<typeof dataViewSelectionPayloadRT>;
export type UnresolvedDatasetSelectionPayload = rt.TypeOf<
typeof unresolvedDatasetSelectionPayloadRT
>;
export type DataSourceSelectionPlain = rt.TypeOf<typeof dataSourceSelectionPlainRT>;
export type DataViewSpecWithId = DataViewSpec & {
id: string;
};
export interface DataSourceSelectionStrategy {
toDataviewSpec(): DataViewSpecWithId;
toPlainSelection(): DataSourceSelectionPlain;
}

View file

@ -1,53 +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 { Dataset } from '../datasets';
import { DataSourceSelectionStrategy, UnresolvedDatasetSelectionPayload } from './types';
export class UnresolvedDatasetSelection implements DataSourceSelectionStrategy {
selectionType: 'unresolved';
selection: {
name?: string;
dataset: Dataset;
};
private constructor(dataset: Dataset) {
this.selectionType = 'unresolved';
this.selection = {
name: dataset.parentIntegration?.name,
dataset,
};
}
toDataviewSpec() {
return this.selection.dataset.toDataviewSpec();
}
toPlainSelection() {
return {
selectionType: this.selectionType,
selection: {
name: this.selection.name,
dataset: this.selection.dataset.toPlain(),
},
};
}
public static fromSelection(selection: UnresolvedDatasetSelectionPayload) {
const { name, dataset } = selection;
// Attempt reconstructing the integration object
const integration = name ? { name } : undefined;
const datasetInstance = Dataset.create(dataset, integration);
return new UnresolvedDatasetSelection(datasetInstance);
}
public static create(dataset: Dataset) {
return new UnresolvedDatasetSelection(dataset);
}
}

View file

@ -1,83 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DataViewDescriptor } from './data_view_descriptor';
describe('DataViewDescriptor', () => {
it('should correctly assert whether a data view has "logs" type', () => {
const id = 'test-id';
// Assert truthy cases
expect(DataViewDescriptor.create({ id, title: 'auditbeat*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'auditbeat-*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'logs*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'logs-*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'logs-*-*' }).isLogsDataType()).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-system.syslog-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-system.syslog-default' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-system.syslog-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({
id,
title: 'cluster1:logs-system.syslog-default',
}).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*,' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({
id,
title: '*:logs-system.syslog-*,*:logs-system.errors-*',
}).isLogsDataType()
).toBeTruthy();
// Assert falsy cases
expect(DataViewDescriptor.create({ id, title: 'auditbeats*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'auditbeats-*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'logss*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'logss-*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'metrics*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'metrics-*' }).isLogsDataType()).toBeFalsy();
expect(
DataViewDescriptor.create({
id,
title: '*:metrics-system.syslog-*,logs-system.errors-*',
}).isLogsDataType()
).toBeFalsy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*,clust,er2:logs-*' }).isLogsDataType()
).toBeFalsy();
expect(
DataViewDescriptor.create({
id,
title: 'cluster1:logs-*, cluster2:logs-*',
}).isLogsDataType()
).toBeFalsy();
});
});

View file

@ -1,100 +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 { createRegExpPatternFrom, testPatternAgainstAllowedList } from '@kbn/data-view-utils';
import { DEFAULT_ALLOWED_LOGS_BASE_PATTERNS } from '@kbn/discover-utils';
import { DataViewSpecWithId } from '../../data_source_selection';
import { DataViewDescriptorType } from '../types';
const LOGS_ALLOWED_LIST = [
createRegExpPatternFrom(DEFAULT_ALLOWED_LOGS_BASE_PATTERNS, 'data'),
// Add more strings or regex patterns as needed
];
type DataViewDescriptorFactoryParams = Omit<DataViewDescriptorType, 'kibanaSpaces'> & {
namespaces?: string[];
};
export class DataViewDescriptor {
id: DataViewDescriptorType['id'];
dataType: DataViewDescriptorType['dataType'];
kibanaSpaces: DataViewDescriptorType['kibanaSpaces'];
name: DataViewDescriptorType['name'];
title: DataViewDescriptorType['title'];
type: DataViewDescriptorType['type'];
private constructor(dataViewDescriptor: DataViewDescriptorType) {
this.id = dataViewDescriptor.id;
this.dataType = dataViewDescriptor.dataType;
this.kibanaSpaces = dataViewDescriptor.kibanaSpaces;
this.name = dataViewDescriptor.name;
this.title = dataViewDescriptor.title;
this.type = dataViewDescriptor.type;
}
getFullTitle() {
return this.name;
}
toDataviewSpec(): DataViewSpecWithId {
return {
id: this.id,
name: this.name,
title: this.title,
};
}
toPlain() {
return {
id: this.id,
dataType: this.dataType,
name: this.name,
title: this.title,
};
}
testAgainstAllowedList(allowedList: string[]) {
return this.title
? testPatternAgainstAllowedList([createRegExpPatternFrom(allowedList, 'data')])(this.title)
: false;
}
public static create({ id, namespaces, title, type, name }: DataViewDescriptorFactoryParams) {
const nameWithFallbackTitle = name ?? title;
const dataType = title ? DataViewDescriptor.#extractDataType(title) : 'unresolved';
const kibanaSpaces = namespaces;
return new DataViewDescriptor({
id,
dataType,
kibanaSpaces,
name: nameWithFallbackTitle,
title,
type,
});
}
static #extractDataType(title: string): DataViewDescriptorType['dataType'] {
if (testPatternAgainstAllowedList(LOGS_ALLOWED_LIST)(title)) {
return 'logs';
}
return 'unknown';
}
public isLogsDataType() {
return this.dataType === 'logs';
}
public isUnknownDataType() {
return this.dataType === 'unknown';
}
public isUnresolvedDataType() {
return this.dataType === 'unresolved';
}
}

View file

@ -1,30 +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 * as rt from 'io-ts';
const dataTypeRT = rt.keyof({
logs: null,
unknown: null,
unresolved: null,
});
export const dataViewDescriptorRT = rt.exact(
rt.intersection([
rt.type({
id: rt.string,
}),
rt.partial({
dataType: dataTypeRT,
kibanaSpaces: rt.array(rt.string),
name: rt.string,
title: rt.string,
type: rt.string,
}),
])
);
export type DataViewDescriptorType = rt.TypeOf<typeof dataViewDescriptorRT>;

View file

@ -1,24 +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.
*/
/* eslint-disable max-classes-per-file */
export class FindIntegrationsError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
this.name = 'FindIntegrationsError';
}
}
export class FindDatasetsError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
this.name = 'FindDatasetsError';
}
}

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './types';
export { Dataset } from './models/dataset';
export { Integration } from './models/integration';

View file

@ -1,93 +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 { IconType } from '@elastic/eui';
import { IndexPattern } from '@kbn/io-ts-utils';
import { TIMESTAMP_FIELD } from '../../constants';
import { DataViewSpecWithId } from '../../data_source_selection';
import { DatasetId, DatasetType, IntegrationType } from '../types';
type IntegrationBase = Partial<Pick<IntegrationType, 'name' | 'title' | 'icons' | 'version'>>;
interface DatasetDeps extends DatasetType {
iconType?: IconType;
}
export class Dataset {
id: DatasetId;
iconType?: IconType;
name: DatasetType['name'];
title: string;
parentIntegration?: IntegrationBase;
private constructor(dataset: DatasetDeps, parentIntegration?: IntegrationBase) {
this.id = `dataset-${dataset.name}` as DatasetId;
this.iconType = dataset.iconType;
this.name = dataset.name;
this.title = dataset.title ?? dataset.name;
this.parentIntegration = parentIntegration && {
name: parentIntegration.name,
title: parentIntegration.title ?? parentIntegration.name,
icons: parentIntegration.icons,
version: parentIntegration.version,
};
}
getFullTitle(): string {
return this.parentIntegration?.title
? `[${this.parentIntegration.title}] ${this.title}`
: this.title;
}
getDatasetWildcard(): IndexPattern {
const [type, dataset, _namespace] = this.name.split('-');
return `${type}-${dataset}-*` as IndexPattern;
}
toDataviewSpec(): DataViewSpecWithId {
// Invert the property because the API returns the index pattern as `name` and a readable name as `title`
return {
id: this.id,
name: this.getFullTitle(),
timeFieldName: TIMESTAMP_FIELD,
title: this.name as string,
};
}
toPlain() {
return {
name: this.name,
title: this.title,
};
}
public static create(dataset: DatasetDeps, parentIntegration?: IntegrationBase) {
const datasetTitle = dataset.title || dataset.name.split('-')[1];
return new Dataset({ ...dataset, title: datasetTitle }, parentIntegration);
}
public static createAllLogsDataset({ indices }: { indices: string }) {
return new Dataset({
name: indices as IndexPattern,
title: 'All logs',
iconType: 'pagesSelect',
});
}
public static createWildcardDatasetsFrom(datasets: Dataset[]) {
// Gather unique list of wildcards
const wildcards = datasets.reduce(
(list, dataset) => list.add(dataset.getDatasetWildcard()),
new Set<IndexPattern>()
);
// Create new datasets for the retrieved wildcards
return Array.from(wildcards).map((wildcard) => Dataset.create({ name: wildcard }));
}
}

View file

@ -1,43 +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 { Dataset } from './dataset';
import { IntegrationId, IntegrationType } from '../types';
export class Integration {
id: IntegrationId;
name: IntegrationType['name'];
title?: IntegrationType['title'];
description?: IntegrationType['description'];
icons?: IntegrationType['icons'];
status: IntegrationType['status'];
version: IntegrationType['version'];
datasets: Dataset[];
private constructor(integration: Integration) {
this.id = integration.id;
this.name = integration.name;
this.title = integration.title;
this.description = integration.description;
this.icons = integration.icons;
this.status = integration.status;
this.version = integration.version;
this.datasets = integration.datasets;
}
public static create(integration: IntegrationType) {
const integrationProps = {
...integration,
id: `integration-${integration.name}-${integration.version}` as IntegrationId,
title: integration.title ?? integration.name,
};
return new Integration({
...integrationProps,
datasets: integration.dataStreams.map((dataset) => Dataset.create(dataset, integrationProps)),
});
}
}

View file

@ -1,57 +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 { indexPatternRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';
export const datasetRT = rt.exact(
rt.intersection([
rt.type({
name: indexPatternRt,
}),
rt.partial({
title: rt.string,
}),
])
);
const integrationStatusRT = rt.keyof({
installed: null,
installing: null,
install_failed: null,
});
const iconRT = rt.intersection([
rt.type({
src: rt.string,
}),
rt.partial({
title: rt.string,
size: rt.string,
type: rt.string,
}),
]);
export const integrationRT = rt.intersection([
rt.type({
name: rt.string,
status: integrationStatusRT,
version: rt.string,
dataStreams: rt.array(datasetRT),
}),
rt.partial({
title: rt.union([rt.string, rt.undefined]),
icons: rt.union([rt.array(iconRT), rt.undefined]),
description: rt.union([rt.string, rt.undefined]),
}),
]);
export type DatasetId = `dataset-${string}`;
export type IntegrationId = `integration-${string}-${string}`;
export type DatasetType = rt.TypeOf<typeof datasetRT>;
export type IntegrationType = rt.TypeOf<typeof integrationRT>;

View file

@ -1,23 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EPM_API_ROUTES } from '@kbn/fleet-plugin/common';
import * as rt from 'io-ts';
/**
* Constants
*/
export const DATASETS_URL = EPM_API_ROUTES.DATA_STREAMS_PATTERN;
export const INTEGRATIONS_URL = EPM_API_ROUTES.INSTALLED_LIST_PATTERN;
/**
* Common types
*/
export const sortOrderRT = rt.keyof({
asc: null,
desc: null,
});
export type SortOrder = rt.TypeOf<typeof sortOrderRT>;

View file

@ -1,30 +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 * as rt from 'io-ts';
import { Dataset } from '../models/dataset';
import { datasetRT } from '../types';
import { sortOrderRT } from './common';
export const findDatasetsResponseRT = rt.type({
items: rt.array(datasetRT),
});
export const findDatasetsRequestQueryRT = rt.exact(
rt.partial({
datasetQuery: rt.string,
type: rt.literal('logs'),
sortOrder: sortOrderRT,
uncategorisedOnly: rt.boolean,
})
);
export type FindDatasetsRequestQuery = rt.TypeOf<typeof findDatasetsRequestQueryRT>;
export type FindDatasetsResponse = rt.TypeOf<typeof findDatasetsResponseRT>;
export interface FindDatasetValue {
items: Dataset[];
}

View file

@ -1,43 +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 { jsonRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';
import { Integration } from '../models/integration';
import { integrationRT } from '../types';
import { sortOrderRT } from './common';
const searchAfterRT = rt.array(rt.union([rt.number, rt.string]));
export const findIntegrationsResponseRT = rt.exact(
rt.intersection([
rt.type({
items: rt.array(integrationRT),
total: rt.number,
}),
rt.partial({
searchAfter: searchAfterRT,
}),
])
);
export const findIntegrationsRequestQueryRT = rt.exact(
rt.partial({
nameQuery: rt.string,
perPage: rt.number,
dataStreamType: rt.literal('logs'),
searchAfter: jsonRt.pipe(searchAfterRT),
sortOrder: sortOrderRT,
})
);
export type SearchAfter = rt.TypeOf<typeof searchAfterRT>;
export type FindIntegrationsRequestQuery = rt.TypeOf<typeof findIntegrationsRequestQueryRT>;
export type FindIntegrationsResponse = rt.TypeOf<typeof findIntegrationsResponseRT>;
export interface FindIntegrationsValue extends Omit<FindIntegrationsResponse, 'items'> {
items: Integration[];
}

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './common';
export * from './find_datasets';
export * from './find_integrations';

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './types';

View file

@ -1,53 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export interface ChartDisplayOptions {
breakdownField: string | null;
}
export type PartialChartDisplayOptions = Partial<ChartDisplayOptions>;
export interface DocumentFieldGridColumnOptions {
type: 'document-field';
field: string;
width?: number;
}
export interface SmartFieldGridColumnOptions {
type: 'smart-field';
smartField: 'content' | 'resource';
fallbackFields: string[];
width?: number;
}
export type GridColumnDisplayOptions = DocumentFieldGridColumnOptions | SmartFieldGridColumnOptions;
export interface GridRowsDisplayOptions {
rowHeight: number;
rowsPerPage: number;
}
export type PartialGridRowsDisplayOptions = Partial<GridRowsDisplayOptions>;
export interface GridDisplayOptions {
columns: GridColumnDisplayOptions[];
rows: GridRowsDisplayOptions;
}
export type PartialGridDisplayOptions = Partial<
Omit<GridDisplayOptions, 'rows'> & { rows?: PartialGridRowsDisplayOptions }
>;
export interface DisplayOptions {
grid: GridDisplayOptions;
chart: ChartDisplayOptions;
}
export interface PartialDisplayOptions {
grid?: PartialGridDisplayOptions;
chart?: PartialChartDisplayOptions;
}

View file

@ -1,41 +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 LRUCache from 'lru-cache';
import hash from 'object-hash';
export interface IHashedCache<KeyType, ValueType> {
get(key: KeyType): ValueType | undefined;
set(key: KeyType, value: ValueType): boolean;
has(key: KeyType): boolean;
reset(): void;
}
export class HashedCache<KeyType extends hash.NotUndefined, ValueType> {
private cache: LRUCache<string, ValueType>;
constructor(options: LRUCache.Options<string, ValueType> = { max: 500 }) {
this.cache = new LRUCache<string, ValueType>(options);
}
public get(key: KeyType): ValueType | undefined {
const serializedKey = hash(key);
return this.cache.get(serializedKey);
}
public set(key: KeyType, value: ValueType) {
const serializedKey = hash(key);
return this.cache.set(serializedKey, value);
}
public has(key: KeyType): boolean {
const serializedKey = hash(key);
return this.cache.has(serializedKey);
}
public reset() {
return this.cache.reset();
}
}

View file

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export {
availableControlPanelFields,
availableControlsPanels,
controlPanelConfigs,
ControlPanelRT,
} from './control_panels';
export type { AvailableControlPanels, ControlPanels } from './control_panels';
export {
AllDatasetSelection,
dataSourceSelectionPlainRT,
singleDatasetSelectionPlainRT,
DataViewSelection,
hydrateDataSourceSelection,
isDatasetSelection,
isDataSourceSelection,
isDataViewSelection,
isUnresolvedDatasetSelection,
UnresolvedDatasetSelection,
} from './data_source_selection';
export type { DataSourceSelectionPlain } from './data_source_selection';
export type {
ChartDisplayOptions,
DisplayOptions,
GridColumnDisplayOptions,
GridDisplayOptions,
GridRowsDisplayOptions,
PartialChartDisplayOptions,
PartialDisplayOptions,
PartialGridDisplayOptions,
PartialGridRowsDisplayOptions,
} from './display_options';
export {
CONTENT_FIELD,
CONTENT_FIELD_CONFIGURATION,
RESOURCE_FIELD_CONFIGURATION,
SMART_FALLBACK_FIELDS,
} from './constants';

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './datasets/v1';

View file

@ -1,9 +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.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface LogsExplorerConfig {}

View file

@ -1,18 +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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/solutions/observability/plugins/logs_explorer'],
coverageDirectory:
'<rootDir>/target/kibana-coverage/jest/x-pack/solutions/observability/plugins/logs_explorer',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/solutions/observability/plugins/logs_explorer/{common,public}/**/*.{ts,tsx}',
],
};

View file

@ -1,40 +0,0 @@
{
"type": "plugin",
"id": "@kbn/logs-explorer-plugin",
"owner": [
"@elastic/obs-ux-logs-team"
],
"group": "observability",
"visibility": "private",
"description": "This plugin provides a LogsExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption.",
"plugin": {
"id": "logsExplorer",
"browser": true,
"server": true,
"configPath": [
"xpack",
"logsExplorer"
],
"requiredPlugins": [
"data",
"dataViews",
"discover",
"fieldFormats",
"navigation",
"share",
"unifiedSearch",
"unifiedDocViewer",
"discoverShared"
],
"optionalPlugins": [],
"requiredBundles": [
"controls",
"fleet",
"kibanaReact",
"kibanaUtils"
],
"extraPublicDirs": [
"common"
]
}
}

View file

@ -1,97 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
export const contentLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.content', {
defaultMessage: 'Content',
});
export const resourceLabel = i18n.translate(
'xpack.logsExplorer.dataTable.header.popover.resource',
{
defaultMessage: 'Resource',
}
);
export const actionFilterForText = (text: string) =>
i18n.translate('xpack.logsExplorer.flyoutDetail.value.hover.filterFor', {
defaultMessage: 'Filter for this {value}',
values: {
value: text,
},
});
export const actionFilterOutText = (text: string) =>
i18n.translate('xpack.logsExplorer.flyoutDetail.value.hover.filterOut', {
defaultMessage: 'Filter out this {value}',
values: {
value: text,
},
});
export const filterOutText = i18n.translate('xpack.logsExplorer.popoverAction.filterOut', {
defaultMessage: 'Filter out',
});
export const filterForText = i18n.translate('xpack.logsExplorer.popoverAction.filterFor', {
defaultMessage: 'Filter for',
});
export const copyValueText = i18n.translate('xpack.logsExplorer.popoverAction.copyValue', {
defaultMessage: 'Copy value',
});
export const copyValueAriaText = (fieldName: string) =>
i18n.translate('xpack.logsExplorer.popoverAction.copyValueAriaText', {
defaultMessage: 'Copy value of {fieldName}',
values: {
fieldName,
},
});
export const openCellActionPopoverAriaText = i18n.translate(
'xpack.logsExplorer.popoverAction.openPopover',
{
defaultMessage: 'Open popover',
}
);
export const closeCellActionPopoverText = i18n.translate(
'xpack.logsExplorer.popoverAction.closePopover',
{
defaultMessage: 'Close popover',
}
);
export const contentHeaderTooltipParagraph1 = (
<FormattedMessage
id="xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1"
defaultMessage="Displays the document's {logLevel} and {message} fields."
values={{
logLevel: <strong>log.level</strong>,
message: <strong>message</strong>,
}}
/>
);
export const contentHeaderTooltipParagraph2 = i18n.translate(
'xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2',
{
defaultMessage: 'When the message field is empty, one of the following is displayed:',
}
);
export const resourceHeaderTooltipParagraph = i18n.translate(
'xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph',
{
defaultMessage: "Fields that provide information on the document's source, such as:",
}
);

View file

@ -1,124 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const POPOVER_ID = 'data-source-selector-popover';
export const INTEGRATIONS_PANEL_ID = 'data-source-selector-integrations-panel';
export const INTEGRATIONS_TAB_ID = 'data-source-selector-integrations-tab';
export const UNCATEGORIZED_PANEL_ID = 'data-source-selector-uncategorized-panel';
export const UNCATEGORIZED_TAB_ID = 'data-source-selector-uncategorized-tab';
export const DATA_VIEWS_PANEL_ID = 'data-source-selector-data-views-panel';
export const DATA_VIEWS_TAB_ID = 'data-source-selector-data-views-tab';
export const DATA_SOURCE_SELECTOR_WIDTH = 520;
export const showAllLogsLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.showAllLogs',
{ defaultMessage: 'Show all logs' }
);
export const addDataLabel = i18n.translate('xpack.logsExplorer.dataSourceSelector.addDataLabel', {
defaultMessage: 'Add data',
});
export const integrationsLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.integrations',
{ defaultMessage: 'Integrations' }
);
export const uncategorizedLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.uncategorized',
{ defaultMessage: 'Uncategorized' }
);
export const dataViewsLabel = i18n.translate('xpack.logsExplorer.dataSourceSelector.dataViews', {
defaultMessage: 'Data Views',
});
export const openDiscoverLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.openDiscover',
{ defaultMessage: 'Opens in Discover' }
);
export const sortOrdersLabel = i18n.translate('xpack.logsExplorer.dataSourceSelector.sortOrders', {
defaultMessage: 'Sort directions',
});
export const noDatasetsLabel = i18n.translate('xpack.logsExplorer.dataSourceSelector.noDatasets', {
defaultMessage: 'No data streams found',
});
export const noDatasetsDescriptionLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.noDatasetsDescription',
{ defaultMessage: 'No datasets or search results found.' }
);
export const noDataViewsLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.noDataViews',
{ defaultMessage: 'No data views found' }
);
export const noDataViewsDescriptionLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.noDataViewsDescription',
{ defaultMessage: 'No data views or search results found.' }
);
export const noIntegrationsLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.noIntegrations',
{ defaultMessage: 'No integrations found' }
);
export const noIntegrationsDescriptionLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.noIntegrationsDescription',
{ defaultMessage: 'No integrations or search results found.' }
);
export const errorLabel = i18n.translate('xpack.logsExplorer.dataSourceSelector.error', {
defaultMessage: 'error',
});
export const noDataRetryLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.noDataRetry',
{ defaultMessage: 'Retry' }
);
export const tryEsql = i18n.translate('xpack.logsExplorer.dataSourceSelector.TryEsql', {
defaultMessage: 'Language: ES|QL',
});
export const technicalPreview = i18n.translate('xpack.logsExplorer.TechPreview', {
defaultMessage: 'Technical preview',
});
export const selectDataViewTypeLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.dataViewFilter.selectDataViewType',
{ defaultMessage: 'Select type' }
);
export const allDataViewTypesLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.dataViewFilter.allDataViewTypes',
{ defaultMessage: 'Show all' }
);
export const logsDataViewTypeLabel = i18n.translate(
'xpack.logsExplorer.dataSourceSelector.dataViewFilter.logsDataViewType',
{ defaultMessage: 'Logs-only' }
);
export const sortOptions = [
{
id: 'asc',
iconType: 'sortAscending',
label: 'Ascending',
},
{
id: 'desc',
iconType: 'sortDescending',
label: 'Descending',
},
];

View file

@ -1,557 +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.
*/
/* eslint-disable no-console */
import React, { useState } from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import type { Meta, Story } from '@storybook/react';
import { IndexPattern } from '@kbn/io-ts-utils';
import { CoreStart } from '@kbn/core/public';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import { DataViewDescriptor } from '../../../common/data_views/models/data_view_descriptor';
import {
AllDatasetSelection,
DataSourceSelectionChangeHandler,
DataSourceSelection,
} from '../../../common/data_source_selection';
import { Dataset, Integration } from '../../../common/datasets';
import { DataSourceSelector } from './data_source_selector';
import { DataSourceSelectorProps, DataSourceSelectorSearchParams } from './types';
import { IsDataViewAvailable } from '../../hooks/use_data_views';
const meta: Meta<typeof DataSourceSelector> = {
component: DataSourceSelector,
title: 'logs_explorer/DataSourceSelector',
decorators: [(wrappedStory) => <I18nProvider>{wrappedStory()}</I18nProvider>],
argTypes: {
datasetsError: {
options: [null, { message: 'Failed to fetch data streams' }],
control: { type: 'radio' },
},
dataViewsError: {
options: [null, { message: 'Failed to fetch data data views' }],
control: { type: 'radio' },
},
integrationsError: {
options: [null, { message: 'Failed to fetch data integrations' }],
control: { type: 'radio' },
},
},
};
export default meta;
const coreMock = {
share: {
url: {
locators: {
get: () => {
return {
useUrl: () => 'http://localhost:5601/app/logs-explorer',
navigate: () => {},
};
},
},
},
},
} as unknown as CoreStart;
const KibanaReactContext = createKibanaReactContext(coreMock);
const DataSourceSelectorTemplate: Story<DataSourceSelectorProps> = (args) => {
const [dataSourceSelection, setDataSourceSelection] = useState<DataSourceSelection>(() =>
AllDatasetSelection.create({ indices: 'logs-*-*' })
);
const [search, setSearch] = useState<DataSourceSelectorSearchParams>({
sortOrder: 'asc',
name: '',
});
const [integrations, setIntegrations] = useState(() => mockIntegrations.slice(0, 10));
const onIntegrationsLoadMore = () => {
if (integrations.length < mockIntegrations.length) {
setIntegrations((prev) => prev.concat(mockIntegrations.slice(prev.length, prev.length + 10)));
}
};
const onSelectionChange: DataSourceSelectionChangeHandler = (newSelection) => {
setDataSourceSelection(newSelection);
};
const isDataViewAvailable: IsDataViewAvailable = (dataView) => {
return true;
};
const filteredIntegrations = integrations.filter((integration) =>
integration.name.includes(search.name as string)
);
const sortedIntegrations =
search.sortOrder === 'asc' ? filteredIntegrations : filteredIntegrations.reverse();
const filteredDatasets = mockDatasets.filter((dataset) =>
dataset.name.includes(search.name as string)
);
const sortedDatasets = search.sortOrder === 'asc' ? filteredDatasets : filteredDatasets.reverse();
const filteredDataViews = mockDataViews.filter((dataView) =>
dataView.name?.includes(search.name as string)
);
const sortedDataViews =
search.sortOrder === 'asc' ? filteredDataViews : filteredDataViews.reverse();
const {
datasetsError,
dataViewsError,
integrationsError,
isLoadingDataViews,
isLoadingIntegrations,
isLoadingUncategorized,
} = args;
return (
<KibanaReactContext.Provider>
<DataSourceSelector
{...args}
datasets={Boolean(datasetsError || isLoadingUncategorized) ? [] : sortedDatasets}
dataViews={Boolean(dataViewsError || isLoadingDataViews) ? [] : sortedDataViews}
dataSourceSelection={dataSourceSelection}
integrations={Boolean(integrationsError || isLoadingIntegrations) ? [] : sortedIntegrations}
isDataViewAvailable={isDataViewAvailable}
onDataViewsSearch={setSearch}
onDataViewsSort={setSearch}
onIntegrationsLoadMore={onIntegrationsLoadMore}
onIntegrationsSearch={setSearch}
onIntegrationsSort={setSearch}
onIntegrationsStreamsSearch={setSearch}
onIntegrationsStreamsSort={setSearch}
onSelectionChange={onSelectionChange}
onUncategorizedSearch={setSearch}
onUncategorizedSort={setSearch}
/>
</KibanaReactContext.Provider>
);
};
export const Basic = DataSourceSelectorTemplate.bind({});
Basic.args = {
datasetsError: null,
dataViewsError: null,
integrationsError: null,
isLoadingDataViews: false,
isLoadingIntegrations: false,
isLoadingUncategorized: false,
isSearchingIntegrations: false,
onDataViewsReload: () => alert('Reload data views...'),
onDataViewsTabClick: () => console.log('Load data views...'),
onIntegrationsReload: () => alert('Reload integrations...'),
onUncategorizedTabClick: () => console.log('Load uncategorized streams...'),
onUncategorizedReload: () => alert('Reloading streams...'),
};
const mockIntegrations: Integration[] = [
{
name: 'system',
version: '1.25.2',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'System metrics stream',
name: 'system-metrics-*' as IndexPattern,
},
{
title: 'System logs stream',
name: 'system-logs-*' as IndexPattern,
},
],
},
{
name: 'kubernetes',
version: '1.35.0',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'Kubernetes metrics stream',
name: 'k8s-metrics-*' as IndexPattern,
},
{
title: 'Kubernetes logs stream',
name: 'k8s-logs-*' as IndexPattern,
},
],
},
{
name: 'mysql',
version: '1.11.0',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'MySQL metrics stream',
name: 'mysql-metrics-*' as IndexPattern,
},
{
title: 'MySQL slow logs stream',
name: 'mysql-slow-logs-*' as IndexPattern,
},
{
title: 'MySQL error logs stream',
name: 'mysql-error-logs-*' as IndexPattern,
},
],
},
{
name: 'apache',
version: '1.12.0',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'Apache metrics stream',
name: 'apache-metrics-*' as IndexPattern,
},
{
title: 'Apache logs stream',
name: 'apache-logs-*' as IndexPattern,
},
{
title: 'Apache error logs stream',
name: 'apache-error-logs-*' as IndexPattern,
},
],
},
{
name: 'nginx',
version: '1.11.1',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'Nginx metrics stream',
name: 'nginx-metrics-*' as IndexPattern,
},
{
title: 'Nginx access logs stream',
name: 'nginx-access-logs-*' as IndexPattern,
},
],
},
{
name: 'postgresql',
version: '1.13.0',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'PostgreSQL metrics stream',
name: 'postgresql-metrics-*' as IndexPattern,
},
{
title: 'PostgreSQL slow query logs stream',
name: 'postgresql-slow-query-logs-*' as IndexPattern,
},
{
title: 'PostgreSQL error logs stream',
name: 'postgresql-error-logs-*' as IndexPattern,
},
],
},
{
name: 'rabbitmq',
version: '1.8.8',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'RabbitMQ metrics stream',
name: 'rabbitmq-metrics-*' as IndexPattern,
},
{
title: 'RabbitMQ queues stream',
name: 'rabbitmq-queues-*' as IndexPattern,
},
{
title: 'RabbitMQ error logs stream',
name: 'rabbitmq-error-logs-*' as IndexPattern,
},
],
},
{
name: 'redis',
version: '1.9.2',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'Redis metrics stream',
name: 'redis-metrics-*' as IndexPattern,
},
{
title: 'Redis slow logs stream',
name: 'redis-slow-logs-*' as IndexPattern,
},
],
},
{
name: 'elasticsearch',
version: '1.5.0',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'Elasticsearch metrics stream',
name: 'elasticsearch-metrics-*' as IndexPattern,
},
{
title: 'Elasticsearch indices stream',
name: 'elasticsearch-indices-*' as IndexPattern,
},
],
},
{
name: 'mongodb',
version: '1.9.3',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'MongoDB metrics stream',
name: 'mongodb-metrics-*' as IndexPattern,
},
{
title: 'MongoDB slow query logs stream',
name: 'mongodb-slow-query-logs-*' as IndexPattern,
},
],
},
{
name: 'prometheus',
version: '1.3.2',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'Prometheus metrics stream',
name: 'prometheus-metrics-*' as IndexPattern,
},
],
},
{
name: 'haproxy',
version: '1.5.1',
status: 'installed' as Integration['status'],
dataStreams: [
{
title: 'HAProxy metrics stream',
name: 'haproxy-metrics-*' as IndexPattern,
},
{
title: 'HAProxy logs stream',
name: 'haproxy-logs-*' as IndexPattern,
},
],
},
{
name: 'atlassian_jira',
version: '1.10.0',
status: 'installed' as Integration['status'],
dataStreams: [
{ title: 'Atlassian metrics stream', name: 'metrics-*' as IndexPattern },
{ title: 'Atlassian secondary', name: 'metrics-*' as IndexPattern },
],
},
{
name: 'atlassian_confluence',
version: '1.10.0',
status: 'installed' as Integration['status'],
dataStreams: [
{ title: 'Atlassian metrics stream', name: 'metrics-*' as IndexPattern },
{ title: 'Atlassian secondary', name: 'metrics-*' as IndexPattern },
],
},
{
name: 'atlassian_bitbucket',
version: '1.9.0',
status: 'installed' as Integration['status'],
dataStreams: [
{ title: 'Atlassian metrics stream', name: 'metrics-*' as IndexPattern },
{ title: 'Atlassian secondary', name: 'metrics-*' as IndexPattern },
],
},
{
name: 'docker',
version: '2.4.3',
status: 'installed' as Integration['status'],
dataStreams: [
{ title: 'Docker container logs', name: 'docker-*' as IndexPattern },
{ title: 'Docker daemon logs', name: 'docker-daemon-*' as IndexPattern },
],
},
{
name: 'aws',
version: '1.36.3',
status: 'installed' as Integration['status'],
dataStreams: [
{ title: 'AWS S3 object access logs', name: 'aws-s3-access-' as IndexPattern },
{ title: 'AWS S3 bucket access logs', name: 'aws-s3-bucket-access-' as IndexPattern },
],
},
{
name: 'cassandra',
version: '1.6.0',
status: 'installed' as Integration['status'],
dataStreams: [
{ title: 'Cassandra server logs', name: 'cassandra-' as IndexPattern },
{ title: 'Cassandra slow queries', name: 'cassandra-slow-' as IndexPattern },
{ title: 'Cassandra errors', name: 'cassandra-errors-' as IndexPattern },
],
},
{
name: 'nginx_ingress_controller',
version: '1.7.1',
status: 'installed' as Integration['status'],
dataStreams: [{ title: 'Nginx ingress logs', name: 'nginx-ingress-' as IndexPattern }],
},
{
name: 'gcp',
version: '2.20.1',
status: 'installed' as Integration['status'],
dataStreams: [{ title: 'GCP Stackdriver logs', name: 'gcp-stackdriver-*' as IndexPattern }],
},
{
name: 'kafka',
version: '1.5.6',
status: 'installed' as Integration['status'],
dataStreams: [{ title: 'Kafka server logs', name: 'kafka-*' as IndexPattern }],
},
{
name: 'kibana',
version: '2.3.4',
status: 'installed' as Integration['status'],
dataStreams: [{ title: 'Kibana server logs', name: 'kibana-*' as IndexPattern }],
},
].map(Integration.create);
const mockDatasets: Dataset[] = [
{ name: 'logs-*' as IndexPattern },
{ name: 'system-logs-*' as IndexPattern },
{ name: 'nginx-logs-*' as IndexPattern },
{ name: 'apache-logs-*' as IndexPattern },
{ name: 'security-logs-*' as IndexPattern },
{ name: 'error-logs-*' as IndexPattern },
{ name: 'access-logs-*' as IndexPattern },
{ name: 'firewall-logs-*' as IndexPattern },
{ name: 'application-logs-*' as IndexPattern },
{ name: 'debug-logs-*' as IndexPattern },
{ name: 'transaction-logs-*' as IndexPattern },
{ name: 'audit-logs-*' as IndexPattern },
{ name: 'server-logs-*' as IndexPattern },
{ name: 'database-logs-*' as IndexPattern },
{ name: 'event-logs-*' as IndexPattern },
{ name: 'auth-logs-*' as IndexPattern },
{ name: 'billing-logs-*' as IndexPattern },
{ name: 'network-logs-*' as IndexPattern },
{ name: 'performance-logs-*' as IndexPattern },
{ name: 'email-logs-*' as IndexPattern },
{ name: 'job-logs-*' as IndexPattern },
{ name: 'task-logs-*' as IndexPattern },
{ name: 'user-logs-*' as IndexPattern },
{ name: 'request-logs-*' as IndexPattern },
{ name: 'payment-logs-*' as IndexPattern },
{ name: 'inventory-logs-*' as IndexPattern },
{ name: 'debugging-logs-*' as IndexPattern },
{ name: 'scheduler-logs-*' as IndexPattern },
{ name: 'diagnostic-logs-*' as IndexPattern },
{ name: 'cluster-logs-*' as IndexPattern },
{ name: 'service-logs-*' as IndexPattern },
{ name: 'framework-logs-*' as IndexPattern },
{ name: 'api-logs-*' as IndexPattern },
{ name: 'load-balancer-logs-*' as IndexPattern },
{ name: 'reporting-logs-*' as IndexPattern },
{ name: 'backend-logs-*' as IndexPattern },
{ name: 'frontend-logs-*' as IndexPattern },
{ name: 'chat-logs-*' as IndexPattern },
{ name: 'error-tracking-logs-*' as IndexPattern },
{ name: 'payment-gateway-logs-*' as IndexPattern },
{ name: 'auth-service-logs-*' as IndexPattern },
{ name: 'billing-service-logs-*' as IndexPattern },
{ name: 'database-service-logs-*' as IndexPattern },
{ name: 'api-gateway-logs-*' as IndexPattern },
{ name: 'event-service-logs-*' as IndexPattern },
{ name: 'notification-service-logs-*' as IndexPattern },
{ name: 'search-service-logs-*' as IndexPattern },
{ name: 'logging-service-logs-*' as IndexPattern },
{ name: 'performance-service-logs-*' as IndexPattern },
{ name: 'load-testing-logs-*' as IndexPattern },
{ name: 'mobile-app-logs-*' as IndexPattern },
{ name: 'web-app-logs-*' as IndexPattern },
{ name: 'stream-processing-logs-*' as IndexPattern },
{ name: 'batch-processing-logs-*' as IndexPattern },
{ name: 'cloud-service-logs-*' as IndexPattern },
{ name: 'container-logs-*' as IndexPattern },
{ name: 'serverless-logs-*' as IndexPattern },
{ name: 'server-administration-logs-*' as IndexPattern },
{ name: 'application-deployment-logs-*' as IndexPattern },
{ name: 'webserver-logs-*' as IndexPattern },
{ name: 'payment-processor-logs-*' as IndexPattern },
{ name: 'inventory-service-logs-*' as IndexPattern },
{ name: 'data-pipeline-logs-*' as IndexPattern },
{ name: 'frontend-service-logs-*' as IndexPattern },
{ name: 'backend-service-logs-*' as IndexPattern },
{ name: 'resource-monitoring-logs-*' as IndexPattern },
{ name: 'logging-aggregation-logs-*' as IndexPattern },
{ name: 'container-orchestration-logs-*' as IndexPattern },
{ name: 'security-audit-logs-*' as IndexPattern },
{ name: 'api-management-logs-*' as IndexPattern },
{ name: 'service-mesh-logs-*' as IndexPattern },
{ name: 'data-processing-logs-*' as IndexPattern },
{ name: 'data-science-logs-*' as IndexPattern },
{ name: 'machine-learning-logs-*' as IndexPattern },
{ name: 'experimentation-logs-*' as IndexPattern },
{ name: 'data-visualization-logs-*' as IndexPattern },
{ name: 'data-cleaning-logs-*' as IndexPattern },
{ name: 'data-transformation-logs-*' as IndexPattern },
{ name: 'data-analysis-logs-*' as IndexPattern },
{ name: 'data-storage-logs-*' as IndexPattern },
{ name: 'data-retrieval-logs-*' as IndexPattern },
{ name: 'data-warehousing-logs-*' as IndexPattern },
{ name: 'data-modeling-logs-*' as IndexPattern },
{ name: 'data-integration-logs-*' as IndexPattern },
{ name: 'data-quality-logs-*' as IndexPattern },
{ name: 'data-security-logs-*' as IndexPattern },
{ name: 'data-encryption-logs-*' as IndexPattern },
{ name: 'data-governance-logs-*' as IndexPattern },
{ name: 'data-compliance-logs-*' as IndexPattern },
{ name: 'data-privacy-logs-*' as IndexPattern },
{ name: 'data-auditing-logs-*' as IndexPattern },
{ name: 'data-discovery-logs-*' as IndexPattern },
{ name: 'data-protection-logs-*' as IndexPattern },
{ name: 'data-archiving-logs-*' as IndexPattern },
{ name: 'data-backup-logs-*' as IndexPattern },
{ name: 'data-recovery-logs-*' as IndexPattern },
{ name: 'data-replication-logs-*' as IndexPattern },
{ name: 'data-synchronization-logs-*' as IndexPattern },
{ name: 'data-migration-logs-*' as IndexPattern },
{ name: 'data-load-balancing-logs-*' as IndexPattern },
{ name: 'data-scaling-logs-*' as IndexPattern },
].map((dataset) => Dataset.create(dataset));
const mockDataViews: DataViewDescriptor[] = [
{
id: 'logs-*',
namespaces: ['default'],
title: 'logs-*',
name: 'logs-*',
},
{
id: 'metrics-*',
namespaces: ['default'],
title: 'metrics-*',
name: 'metrics-*',
},
{
id: '7258d186-6430-4b51-bb67-2603cdfb4652',
namespaces: ['default'],
title: 'synthetics-*',
typeMeta: {},
name: 'synthetics-dashboard',
},
].map((dataView) => DataViewDescriptor.create(dataView));

View file

@ -1,329 +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 { EuiContextMenu, EuiFlexGroup, EuiHorizontalRule, EuiTab, EuiTabs } from '@elastic/eui';
import styled from '@emotion/styled';
import React, { useMemo } from 'react';
import { useIntersectionRef } from '../../hooks/use_intersection_ref';
import { getDataViewTestSubj } from '../../utils/get_data_view_test_subj';
import {
dataViewsLabel,
DATA_VIEWS_PANEL_ID,
DATA_VIEWS_TAB_ID,
DATA_SOURCE_SELECTOR_WIDTH,
integrationsLabel,
INTEGRATIONS_PANEL_ID,
INTEGRATIONS_TAB_ID,
uncategorizedLabel,
UNCATEGORIZED_PANEL_ID,
UNCATEGORIZED_TAB_ID,
} from './constants';
import { useDataSourceSelector } from './state_machine/use_data_source_selector';
import { SelectorPopover } from './sub_components/selector_popover';
import { DataViewMenuItem } from './sub_components/data_view_menu_item';
import { SearchControls } from './sub_components/search_controls';
import { ESQLButton, SelectorFooter, ShowAllLogsButton } from './sub_components/selector_footer';
import { DataSourceSelectorProps } from './types';
import {
buildIntegrationsTree,
createDataViewsStatusItem,
createIntegrationStatusItem,
createUncategorizedStatusItem,
} from './utils';
import { AddDataButton } from './sub_components/add_data_button';
import { DataViewsFilter } from './sub_components/data_view_filter';
export function DataSourceSelector({
datasets,
dataSourceSelection,
allSelection,
datasetsError,
dataViews,
dataViewCount,
dataViewsError,
discoverEsqlUrlProps,
integrations,
integrationsError,
isDataViewAllowed,
isDataViewAvailable,
isEsqlEnabled,
isLoadingDataViews,
isLoadingIntegrations,
isLoadingUncategorized,
isSearchingIntegrations,
onDataViewsReload,
onDataViewsSearch,
onDataViewsFilter,
onDataViewsSort,
onDataViewsTabClick,
onIntegrationsLoadMore,
onIntegrationsReload,
onIntegrationsSearch,
onIntegrationsSort,
onIntegrationsStreamsSearch,
onIntegrationsStreamsSort,
onSelectionChange,
onUncategorizedReload,
onUncategorizedSearch,
onUncategorizedSort,
onUncategorizedTabClick,
}: DataSourceSelectorProps) {
const {
panelId,
search,
dataViewsFilter,
tabId,
isOpen,
isAllMode,
changePanel,
closePopover,
scrollToIntegrationsBottom,
searchByName,
filterByType,
selectAllLogs,
selectDataset,
selectDataView,
sortByOrder,
switchToIntegrationsTab,
switchToUncategorizedTab,
switchToDataViewsTab,
togglePopover,
} = useDataSourceSelector({
initialContext: { selection: dataSourceSelection },
onDataViewsSearch,
onDataViewsFilter,
onDataViewsSort,
onIntegrationsLoadMore,
onIntegrationsReload,
onIntegrationsSearch,
onIntegrationsSort,
onIntegrationsStreamsSearch,
onIntegrationsStreamsSort,
onUncategorizedSearch,
onUncategorizedSort,
onUncategorizedReload,
onSelectionChange,
});
const [setSpyRef] = useIntersectionRef({ onIntersecting: scrollToIntegrationsBottom });
const { items: integrationItems, panels: integrationPanels } = useMemo(() => {
if (!integrations || integrations.length === 0) {
return {
items: [
createIntegrationStatusItem({
data: integrations,
error: integrationsError,
isLoading: isLoadingIntegrations,
onRetry: onIntegrationsReload,
}),
],
panels: [],
};
}
return buildIntegrationsTree({
integrations,
onDatasetSelected: selectDataset,
spyRef: setSpyRef,
});
}, [
integrations,
integrationsError,
isLoadingIntegrations,
selectDataset,
onIntegrationsReload,
setSpyRef,
]);
const uncategorizedItems = useMemo(() => {
if (!datasets || datasets.length === 0) {
return [
createUncategorizedStatusItem({
data: datasets,
error: datasetsError,
isLoading: isLoadingUncategorized,
onRetry: onUncategorizedReload,
}),
];
}
return datasets.map((dataset) => ({
name: dataset.title,
onClick: () => selectDataset(dataset),
}));
}, [datasets, datasetsError, isLoadingUncategorized, selectDataset, onUncategorizedReload]);
const dataViewsItems = useMemo(() => {
if (!dataViews || dataViews.length === 0) {
return [
createDataViewsStatusItem({
data: dataViews,
error: dataViewsError,
isLoading: isLoadingDataViews,
onRetry: onDataViewsReload,
}),
];
}
return dataViews.map((dataView) => ({
'data-test-subj': getDataViewTestSubj(dataView.title),
name: <DataViewMenuItem dataView={dataView} isAvailable={isDataViewAllowed(dataView)} />,
onClick: () => selectDataView(dataView),
disabled: !isDataViewAvailable(dataView),
}));
}, [
dataViews,
dataViewsError,
isDataViewAllowed,
isDataViewAvailable,
isLoadingDataViews,
onDataViewsReload,
selectDataView,
]);
const tabs = [
{
id: INTEGRATIONS_TAB_ID,
name: integrationsLabel,
onClick: switchToIntegrationsTab,
'data-test-subj': 'dataSourceSelectorIntegrationsTab',
},
{
id: UNCATEGORIZED_TAB_ID,
name: uncategorizedLabel,
onClick: () => {
onUncategorizedTabClick(); // Lazy-load uncategorized datasets only when accessing the Uncategorized tab
switchToUncategorizedTab();
},
'data-test-subj': 'dataSourceSelectorUncategorizedTab',
},
{
id: DATA_VIEWS_TAB_ID,
name: dataViewsLabel,
onClick: () => {
onDataViewsTabClick(); // Lazy-load data views only when accessing the Data Views tab
switchToDataViewsTab();
},
'data-test-subj': 'dataSourceSelectorDataViewsTab',
},
];
const tabEntries = tabs.map((tab) => (
<EuiTab
key={tab.id}
onClick={tab.onClick}
isSelected={tab.id === tabId}
data-test-subj={tab['data-test-subj']}
>
{tab.name}
</EuiTab>
));
return (
<SelectorPopover
selection={dataSourceSelection}
isOpen={isOpen}
closePopover={closePopover}
onClick={togglePopover}
>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<Tabs bottomBorder={false}>{tabEntries}</Tabs>
{(tabId === INTEGRATIONS_TAB_ID || tabId === UNCATEGORIZED_TAB_ID) && <AddDataButton />}
</EuiFlexGroup>
<EuiHorizontalRule margin="none" />
<SearchControls
key={panelId}
search={search}
onSearch={searchByName}
onSort={sortByOrder}
isLoading={isSearchingIntegrations || isLoadingUncategorized}
filterComponent={
tabId === DATA_VIEWS_TAB_ID && (
<DataViewsFilter
filter={dataViewsFilter}
count={dataViewCount}
onFilter={filterByType}
/>
)
}
/>
<EuiHorizontalRule margin="none" />
{/* For a smoother user experience, we keep each tab content mount and we only show the select one
"hiding" all the others. Unmounting mounting each tab content on change makes it feel glitchy,
while the tradeoff of keeping the contents in memory provide a better UX. */}
{/* Integrations tab content */}
<ContextMenu
hidden={tabId !== INTEGRATIONS_TAB_ID}
initialPanelId={panelId}
panels={[
{
id: INTEGRATIONS_PANEL_ID,
title: integrationsLabel,
width: DATA_SOURCE_SELECTOR_WIDTH,
items: integrationItems,
},
...integrationPanels,
]}
onPanelChange={changePanel}
className="eui-yScroll"
data-test-subj="integrationsContextMenu"
size="s"
/>
{/* Uncategorized tab content */}
<ContextMenu
hidden={tabId !== UNCATEGORIZED_TAB_ID}
initialPanelId={UNCATEGORIZED_PANEL_ID}
panels={[
{
id: UNCATEGORIZED_PANEL_ID,
title: uncategorizedLabel,
width: DATA_SOURCE_SELECTOR_WIDTH,
items: uncategorizedItems,
},
]}
className="eui-yScroll"
data-test-subj="uncategorizedContextMenu"
size="s"
/>
{/* Data views tab content */}
<ContextMenu
hidden={tabId !== DATA_VIEWS_TAB_ID}
initialPanelId={DATA_VIEWS_PANEL_ID}
panels={[
{
id: DATA_VIEWS_PANEL_ID,
width: DATA_SOURCE_SELECTOR_WIDTH,
items: dataViewsItems,
},
]}
className="eui-yScroll"
data-test-subj="dataViewsContextMenu"
size="s"
/>
<EuiHorizontalRule margin="none" />
<SelectorFooter>
<ShowAllLogsButton
isSelected={isAllMode}
onClick={selectAllLogs}
allSelection={allSelection}
/>
{isEsqlEnabled && <ESQLButton {...discoverEsqlUrlProps} />}
</SelectorFooter>
</SelectorPopover>
);
}
const Tabs = styled(EuiTabs)`
padding: 0 8px;
`;
const ContextMenu = styled(EuiContextMenu)`
max-height: 440px;
transition: none !important;
`;

View file

@ -1,9 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './data_source_selector';
export * from './types';

View file

@ -1,27 +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 { DEFAULT_ALL_SELECTION } from '../../../state_machines/logs_explorer_controller';
import { HashedCache } from '../../../../common/hashed_cache';
import { INTEGRATIONS_PANEL_ID, INTEGRATIONS_TAB_ID } from '../constants';
import { DataSourceSelectorSearchParams } from '../types';
import { DefaultDataSourceSelectorContext } from './types';
export const defaultSearch: DataSourceSelectorSearchParams = {
name: '',
sortOrder: 'asc',
};
export const DEFAULT_CONTEXT: DefaultDataSourceSelectorContext = {
selection: DEFAULT_ALL_SELECTION,
allSelection: DEFAULT_ALL_SELECTION,
searchCache: new HashedCache(),
panelId: INTEGRATIONS_PANEL_ID,
tabId: INTEGRATIONS_TAB_ID,
search: defaultSearch,
dataViewsFilter: {},
};

View file

@ -1,9 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './state_machine';
export * from './types';

View file

@ -1,360 +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 { actions, assign, createMachine, raise } from 'xstate';
import {
DataViewSelection,
isAllDatasetSelection,
isDataViewSelection,
SingleDatasetSelection,
} from '../../../../common/data_source_selection';
import { DATA_VIEWS_TAB_ID, INTEGRATIONS_TAB_ID, UNCATEGORIZED_TAB_ID } from '../constants';
import { defaultSearch, DEFAULT_CONTEXT } from './defaults';
import {
DataSourceSelectorContext,
DataSourceSelectorEvent,
DataSourceSelectorStateMachineDependencies,
DataSourceSelectorTypestate,
DefaultDataSourceSelectorContext,
} from './types';
export const createPureDataSourceSelectorStateMachine = (
initialContext: Partial<DefaultDataSourceSelectorContext> = DEFAULT_CONTEXT
) =>
/** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSACeiACwAmXdQDMANmMB2UwYAcu4wEYH+gDQgAHogdmj5yU9MATmMAVmNjfQcAXyi3NExsXAJiMio6BnJmGgYwNS4AYWE+PAkZTQVlVQ0kD0QbCwtqSX1TSXNAh0lJQIsQtx0EUycmyTNRm2abSOMYuIwsHHwiUgoaeiYWahy8-iExKVkaipV1TU8EGxsHE2ddc11dBwtAyP7EEN0Q6lMLfS6Ohy6QK6eqzEDxBZJZapNYZLJbWi5LglMT5HgAfQAgsJhOiigI8OjkJieJiSjwDuUOJVTjVzjYftQLA4JsZgm1eoFTG9BlMTL1wqNAiEGfowRDEksUqt0htsoi1NRFGp0GAoJQMFVYDxUAAjZEAdQAkjx8gAJdH8dEAVQAcvkSaIBHwAEpGgBaomQlsxACFKUdqSdqqBzgBaJ6SajCwLA5n+YxTYw8pzGKNWfT6YwPX4OEIi8XzSXJFZpdaZTbbJUqtUa4PavWGk3my18IkkzHogBqRtEBsJpP9ZUDSmDZ0QkX01AM1m6jjzbI+Ke86dMmezulz+ZshYSixLMNlFfluWrqvVmvUDd11EIilgKjUUCNNYv9YKZsxtoEonRAAUv1EYQA3kIMqnHBBOkCagWn0D4bDMIIJlMGweV6a5hTZIF9CsZoWV3SEpVLWE5QRU9lXPOstR1G87wfZVn1fKir2RfIXT4HFW3RI1bR4J0XRJI0+FtQlfT4Hh+AAWRAkBjnAulEDDTMvhCCxs0cO5QhZQIeVUqcggcYJVLMCIHFMAji2hGVy3hKsKNrS81GvW970fRjKMc2BkVETEXRbX0AE10VtTFJNKQ5QNHeTQ0U+wvjaSI7BCLpLE+XQeWsaC4JQsILAZBpolicEi33KyyzhSsFTPBz6xolz6KfF8PPfPBXQxQL0VdZBRBdGS5NpGKEDDdTqBCQwfhwl4nCXbQ6nzUbmkkZ5TCGHpLAs0rpXK0i7KYzy6rotympqqo8HQSgwFQABbLzzS-H9-0A4Dh0imkQ1qSDuhgtd4MQwJkL6WaEAsfwmiGXpcr+SxzKKiVNuIo9bKq+y32ovV6qOvbgzOi7rq8kpfP8oKQrCvqwIGj6QhZb5ksTTcWgaMbdJCLKFzuYJnnsQINqhLaSOPMjFRR5inIO1yGOO1H1Bxy6buRNr0Q6rqerJqKKfOLNGkkGxY30DpYxQgxmanYEnEnRMGQMHmiMPGzKvIrG0do8XGsd6Xztl-GgNENF21JclVbeiCGRsb5-vqSQRVGbp0qBqxrhQyafoCFprYPayKpPRUAFc1BIDA1SoRQAC9IBoptTQtK0eL4gQBJ4ISRJ9IcItk8n3vDcIpyzFpujgkGQYZFM2iMUJWgZ-6swZNOyv5pHT1z-PzyL0uIHLvBjUrrjiVJbte37ZvA7HBSEEMb41zXXRxkMkIfmTIHnGFaNJ4MYIek3HdYZK3mEbtrPqEXgXKAK8y6NgJn5C0HUSbhSpGrDusUJjfAmGEFamYLBAkBgMbwZ8ngg2BCPNkPwZ580RvbHOecgEgLXmAhWSsXTdV6i9NucCIJhnpt8Kwa5fhqW6MlTBXhvCmFGq0Z4aYFymDCMQ3+mdBYAIocvSgJdQH6hRD7DEO8ySiApEw-q8CLgoTDpcR4QQ9ZDBTPYUO3R-rBB+J8FCMM5h7h-rbGRVYIDzC7IoMAAB3a8FcWzV14vxQSwkBx+iPtFD6ZgTY3x6CzdorJh6qVGn8BCV8PjOEKo4wi6dtoCzcR4rxvj16bwCW2O0Dpa6ug9F6Q+Oj24QUBCYVonwB6RzzMlVCD9DBfDuPUMIdhIgXykS4naVV3GYE8T4vx4CibBVCjAkcQcT5DCjC8EEmSuj5l6CmCI0FEx4RwtrOCmYRkZzGaeCZqApnFJoS6dqQVlaMNbroiCqyTB3BQrGOMLNXAP28NcYxa5Yx5h6HrM5eT56KiuTcmZ3tfYaL3n2CJ6s6jWGjICLoSYr71FjlgrM1xwg-HqFcewvQxRfycTbGU2BpRVGoIwVAd4JkMXTlULgKK9FhmSgncGLw1IvBwk8FM6CjD9LMDYFmeYswhAhTQWlKx6WMuZZqJ8bL1AcocC8hpJ9uV-GoGZLkkcsyCt+CmVk0YeGGDsNK0IcrqAKrIEqpligWVqqsuy8Q+htUsN1WEKMXzBEglNX8rBoomSJkyjasadrKU5Nng6j16gHUMWIN5VEGJsS4nxISDRAd6m+sGk4UOjg8orSpvYUIeURVsmaZ8Eli4hj2sdcGFNT402qIRR2fNPrlmDSUpIIwBtMo4UnMKFMEiTZjUCEtfQlxko4WbUmxUSh21gHTWov2nYezIoLX2j6SlgTTlCFmZobI0HCofh0IR2twhpjUhsuCS66XJqZYQDdXb-ZaM5RBSw0Fb4vEjihUYVhJUTsZMGzWdg7BLQccVKluT5XLuoG+j96iOxIoND+k+lhjDRi5iDE1EwDD3wGIlIRC7lJJTaM+xVyaYVFLQ1iTiOat09tgfu84xaTAin5cyemm5uRA1SUIrSeZcE-EHbRp19HCk+KY3m79e7j79rytQS4kwJVX2zJHFM16mgIUFA+q4T642WRIS2+lDH5OdvQ7vHdWHlORPDIZa4t6Z2gbypEHZwmnhCJ1hp1oHQY2yrBGocgEA4CaDhs41YHGVMHq4WDRKnwUroP4cDPDwRQhX0Hl8l49q-7xec4pVS0EEoslSyBz4E7AXhA+EtbW+WKXZPM9I0ibAOCQGK6iyCZk8NHPQYZUIXcHA8gMKPcwVhbD2DNoV1xCoetcrCG5tclXkrVYy9a6M9XctNZ1gVsz8NRn5KqgAC1cktt5anJ5U2ZGpQwZqgZEuPTlxrIdYzzYuULN2os9RXd1SCPDFXqObbxXoamDwzbOAttYFr8H40kL-rI4W+10YXYfADotzgTDTCSitTcN8eTBFDg0cIeUyeAmZF907DtmpOwxhLX78AlkJfDH8G9nQdbGMlZKuCGV0EGtS3lNk-1Cc06hdVKWf3nYNXcidd2uMbpY4+nrKcowWa-BBBrkUGVLg7Z+BI3CtNuZHdi5Cshcil6F0UavGiKv2cMnU94GxBhVJBHHd06mc6DaLga2pCXlvrO3N1A7xAplviAg6PO7Xa4UyRC+B0DBExLajGkwl15fq1OBp8DrNBoavC316dO2d87mgWHT86lVbl1XvUz-2oEeGVp5klYOqbM0sHlujPmUI2tUtwZi9StIlnk2rqgMQMPQ19V3sTNmdcykcIit6M7hodgCeTgr2bofSGX2KjfZP7MjQzANGaBIwdAG9MGCZJJro5+Vp5Ur7JyZRTJ-DSnHwiIDWWQi892R5wU5iM9Z6Z+kKUYggA */
createMachine<DataSourceSelectorContext, DataSourceSelectorEvent, DataSourceSelectorTypestate>(
{
context: { ...DEFAULT_CONTEXT, ...initialContext },
preserveActionOrder: true,
predictableActionArguments: true,
id: 'DataSourceSelector',
type: 'parallel',
states: {
popover: {
initial: 'closed',
states: {
closed: {
id: 'closed',
on: {
TOGGLE: 'open.hist',
},
},
open: {
initial: 'integrationsTab',
on: {
CLOSE: 'closed',
TOGGLE: 'closed',
SELECT_ALL_LOGS: 'closed',
},
states: {
hist: {
type: 'history',
history: 'deep',
},
integrationsTab: {
initial: 'listingIntegrations',
entry: ['storeIntegrationsTabId'],
on: {
SWITCH_TO_UNCATEGORIZED_TAB: 'uncategorizedTab',
SWITCH_TO_DATA_VIEWS_TAB: 'dataViewsTab',
},
states: {
hist: {
type: 'history',
},
listingIntegrations: {
entry: [
'storePanelId',
'retrieveSearchFromCache',
'maybeRestoreSearchResult',
],
on: {
CHANGE_PANEL: 'listingIntegrationStreams',
SCROLL_TO_INTEGRATIONS_BOTTOM: {
actions: 'loadMoreIntegrations',
},
SEARCH_BY_NAME: {
actions: ['storeSearch', 'searchIntegrations'],
},
SORT_BY_ORDER: {
actions: ['storeSearch', 'sortIntegrations'],
},
},
},
listingIntegrationStreams: {
entry: [
'storePanelId',
'retrieveSearchFromCache',
'maybeRestoreSearchResult',
],
on: {
CHANGE_PANEL: 'listingIntegrations',
SEARCH_BY_NAME: {
actions: ['storeSearch', 'searchIntegrationsStreams'],
},
SORT_BY_ORDER: {
actions: ['storeSearch', 'sortIntegrationsStreams'],
},
SELECT_DATASET: '#closed',
},
},
},
},
uncategorizedTab: {
entry: [
'storeUncategorizedTabId',
'retrieveSearchFromCache',
'maybeRestoreSearchResult',
],
on: {
SWITCH_TO_INTEGRATIONS_TAB: 'integrationsTab.hist',
SWITCH_TO_DATA_VIEWS_TAB: 'dataViewsTab',
SEARCH_BY_NAME: {
actions: ['storeSearch', 'searchUncategorized'],
},
SORT_BY_ORDER: {
actions: ['storeSearch', 'sortUncategorized'],
},
SELECT_DATASET: '#closed',
},
},
dataViewsTab: {
entry: [
'storeDataViewsTabId',
'retrieveSearchFromCache',
'maybeRestoreSearchResult',
],
on: {
SWITCH_TO_INTEGRATIONS_TAB: 'integrationsTab.hist',
SWITCH_TO_UNCATEGORIZED_TAB: 'uncategorizedTab',
SEARCH_BY_NAME: {
actions: ['storeSearch', 'searchDataViews'],
},
FILTER_BY_TYPE: {
actions: ['storeDataViewFilter', 'filterDataViews'],
},
SORT_BY_ORDER: {
actions: ['storeSearch', 'sortDataViews'],
},
SELECT_DATA_VIEW: {
target: '#closed',
actions: ['storeDataViewSelection'],
},
},
},
},
},
},
},
selection: {
initial: 'validatingSelection',
states: {
validatingSelection: {
always: [
{ cond: 'isDataViewSelection', target: 'dataView' },
{ cond: 'isAllDatasetSelection', target: 'all' },
{ target: 'single' },
],
},
single: {
on: {
SELECT_ALL_LOGS: {
actions: ['storeAllSelection', 'notifySelectionChanged'],
target: 'all',
},
SELECT_DATASET: {
actions: ['storeSingleSelection', 'notifySelectionChanged'],
},
SELECT_DATA_VIEW: {
actions: ['storeDataViewSelection', 'notifySelectionChanged'],
target: 'dataView',
},
},
},
all: {
on: {
SELECT_DATASET: {
actions: ['storeSingleSelection', 'notifySelectionChanged'],
target: 'single',
},
SELECT_DATA_VIEW: {
actions: ['storeDataViewSelection', 'notifySelectionChanged'],
target: 'dataView',
},
},
},
dataView: {
on: {
SELECT_ALL_LOGS: {
actions: ['storeAllSelection', 'notifySelectionChanged'],
target: 'all',
},
SELECT_DATASET: {
actions: ['storeSingleSelection', 'notifySelectionChanged'],
target: 'single',
},
SELECT_DATA_VIEW: {
actions: ['storeDataViewSelection', 'notifySelectionChanged'],
},
},
},
},
},
},
},
{
actions: {
storeIntegrationsTabId: assign((_context) => ({ tabId: INTEGRATIONS_TAB_ID })),
storeUncategorizedTabId: assign((_context) => ({ tabId: UNCATEGORIZED_TAB_ID })),
storeDataViewsTabId: assign((_context) => ({ tabId: DATA_VIEWS_TAB_ID })),
storePanelId: assign((_context, event) =>
'panelId' in event ? { panelId: event.panelId } : {}
),
storeSearch: assign((context, event) => {
if ('search' in event) {
const id = context.tabId === INTEGRATIONS_TAB_ID ? context.panelId : context.tabId;
context.searchCache.set(id, event.search);
return {
search: event.search,
};
}
return {};
}),
storeDataViewFilter: assign((context, event) => {
if (event.type === 'FILTER_BY_TYPE') {
return { dataViewsFilter: event.filter };
}
return {};
}),
storeAllSelection: assign((_context) => ({
selection: _context.allSelection,
})),
storeSingleSelection: assign((_context, event) =>
event.type === 'SELECT_DATASET'
? { selection: SingleDatasetSelection.create(event.selection) }
: {}
),
storeDataViewSelection: assign((_context, event) =>
event.type === 'SELECT_DATA_VIEW'
? { selection: DataViewSelection.create(event.selection) }
: {}
),
retrieveSearchFromCache: assign((context, event) => {
if (event.type === 'CHANGE_PANEL' && 'panelId' in event) {
return { search: context.searchCache.get(event.panelId) ?? defaultSearch };
}
if (event.type === 'SWITCH_TO_INTEGRATIONS_TAB' && 'panelId' in context) {
return { search: context.searchCache.get(context.panelId) ?? defaultSearch };
}
if (event.type === 'SWITCH_TO_UNCATEGORIZED_TAB' && 'tabId' in context) {
return { search: context.searchCache.get(context.tabId) ?? defaultSearch };
}
if (event.type === 'SWITCH_TO_DATA_VIEWS_TAB' && 'tabId' in context) {
return { search: context.searchCache.get(context.tabId) ?? defaultSearch };
}
return {};
}),
maybeRestoreSearchResult: actions.pure((context, event) => {
const hasSearchOnChangePanel =
event.type === 'CHANGE_PANEL' && context.searchCache.has(event.panelId);
const hasSearchOnIntegrationsTab =
event.type === 'SWITCH_TO_INTEGRATIONS_TAB' && context.searchCache.has(context.panelId);
const hasSearchOnUncategorizedTab =
event.type === 'SWITCH_TO_UNCATEGORIZED_TAB' && context.searchCache.has(context.tabId);
const hasSearchOnDataViewsTab =
event.type === 'SWITCH_TO_DATA_VIEWS_TAB' && context.searchCache.has(context.tabId);
if (
hasSearchOnChangePanel ||
hasSearchOnIntegrationsTab ||
hasSearchOnUncategorizedTab ||
hasSearchOnDataViewsTab
) {
return raise({ type: 'SORT_BY_ORDER', search: context.search });
}
}),
},
guards: {
isDataViewSelection: (context) => isDataViewSelection(context.selection),
isAllDatasetSelection: (context) => isAllDatasetSelection(context.selection),
},
}
);
export const createDataSourceSelectorStateMachine = ({
initialContext,
onDataViewsFilter,
onDataViewsSearch,
onDataViewsSort,
onIntegrationsLoadMore,
onIntegrationsReload,
onIntegrationsSearch,
onIntegrationsSort,
onIntegrationsStreamsSearch,
onIntegrationsStreamsSort,
onUncategorizedSearch,
onUncategorizedSort,
onSelectionChange,
onUncategorizedReload,
}: DataSourceSelectorStateMachineDependencies) =>
createPureDataSourceSelectorStateMachine(initialContext).withConfig({
actions: {
notifySelectionChanged: (context) => {
return onSelectionChange(context.selection);
},
loadMoreIntegrations: onIntegrationsLoadMore,
relaodIntegrations: onIntegrationsReload,
reloadUncategorized: onUncategorizedReload,
// Search actions
searchIntegrations: (_context, event) => {
if ('search' in event) {
onIntegrationsSearch(event.search);
}
},
sortIntegrations: (_context, event) => {
if ('search' in event) {
onIntegrationsSort(event.search);
}
},
searchDataViews: (context, event) => {
if ('search' in event) {
onDataViewsSearch(event.search);
}
},
filterDataViews: (context, event) => {
if ('filter' in event) {
onDataViewsFilter(event.filter);
}
},
sortDataViews: (context, event) => {
if ('search' in event) {
onDataViewsSort(event.search);
}
},
searchIntegrationsStreams: (context, event) => {
if ('search' in event) {
onIntegrationsStreamsSearch({ ...event.search, integrationId: context.panelId });
}
},
sortIntegrationsStreams: (context, event) => {
if ('search' in event) {
onIntegrationsStreamsSort({ ...event.search, integrationId: context.panelId });
}
},
searchUncategorized: (_context, event) => {
if ('search' in event) {
onUncategorizedSearch(event.search);
}
},
sortUncategorized: (_context, event) => {
if ('search' in event) {
onUncategorizedSort(event.search);
}
},
},
});

View file

@ -1,157 +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 { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor';
import { FilterDataViews, SearchDataViews } from '../../../hooks/use_data_views';
import {
AllDatasetSelection,
DataSourceSelection,
DataSourceSelectionChangeHandler,
} from '../../../../common/data_source_selection';
import { Dataset } from '../../../../common/datasets/models/dataset';
import { ReloadDatasets, SearchDatasets } from '../../../hooks/use_datasets';
import {
LoadMoreIntegrations,
ReloadIntegrations,
SearchIntegrations,
} from '../../../hooks/use_integrations';
import type { IHashedCache } from '../../../../common/hashed_cache';
import { DataSourceSelectorSearchParams, PanelId, TabId } from '../types';
import { DataViewsFilterParams } from '../../../state_machines/data_views';
export interface DefaultDataSourceSelectorContext {
selection: DataSourceSelection;
allSelection: AllDatasetSelection;
tabId: TabId;
panelId: PanelId;
searchCache: IHashedCache<PanelId | TabId, DataSourceSelectorSearchParams>;
search: DataSourceSelectorSearchParams;
dataViewsFilter: DataViewsFilterParams;
}
export type DataSourceSelectorTypestate =
| {
value: 'popover';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.closed';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.open';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.open.hist';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.open.integrationsTab';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.open.integrationsTab.listingIntegrations';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.open.integrationsTab.listingIntegrationStreams';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.open.uncategorizedTab';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'popover.open.dataViewsTab';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'selection';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'selection.validatingSelection';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'selection.single';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'selection.dataView';
context: DefaultDataSourceSelectorContext;
}
| {
value: 'selection.all';
context: DefaultDataSourceSelectorContext;
};
export type DataSourceSelectorContext = DataSourceSelectorTypestate['context'];
export type DataSourceSelectorEvent =
| {
type: 'CLOSE';
}
| {
type: 'TOGGLE';
}
| {
type: 'SWITCH_TO_INTEGRATIONS_TAB';
}
| {
type: 'SWITCH_TO_UNCATEGORIZED_TAB';
}
| {
type: 'SWITCH_TO_DATA_VIEWS_TAB';
}
| {
type: 'CHANGE_PANEL';
panelId: PanelId;
}
| {
type: 'SELECT_DATASET';
selection: Dataset;
}
| {
type: 'SELECT_DATA_VIEW';
selection: DataViewDescriptor;
}
| {
type: 'FILTER_BY_TYPE';
filter: DataViewsFilterParams;
}
| {
type: 'SELECT_ALL_LOGS';
}
| {
type: 'SCROLL_TO_INTEGRATIONS_BOTTOM';
}
| {
type: 'SEARCH_BY_NAME';
search: DataSourceSelectorSearchParams;
}
| {
type: 'SORT_BY_ORDER';
search: DataSourceSelectorSearchParams;
};
export interface DataSourceSelectorStateMachineDependencies {
initialContext?: Partial<DefaultDataSourceSelectorContext>;
onDataViewsSearch: SearchDataViews;
onDataViewsFilter: FilterDataViews;
onDataViewsSort: SearchDataViews;
onIntegrationsLoadMore: LoadMoreIntegrations;
onIntegrationsReload: ReloadIntegrations;
onIntegrationsSearch: SearchIntegrations;
onIntegrationsSort: SearchIntegrations;
onIntegrationsStreamsSearch: SearchIntegrations;
onIntegrationsStreamsSort: SearchIntegrations;
onSelectionChange: DataSourceSelectionChangeHandler;
onUncategorizedReload: ReloadDatasets;
onUncategorizedSearch: SearchDatasets;
onUncategorizedSort: SearchDatasets;
}

View file

@ -1,169 +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 { useCallback } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { isAllDatasetSelection } from '../../../../common/data_source_selection';
import {
ChangePanelHandler,
DatasetSelectionHandler,
DataSourceSelectorSearchHandler,
DataViewFilterHandler,
DataViewSelectionHandler,
PanelId,
} from '../types';
import { createDataSourceSelectorStateMachine } from './state_machine';
import { DataSourceSelectorStateMachineDependencies } from './types';
export const useDataSourceSelector = ({
initialContext,
onDataViewsSearch,
onDataViewsFilter,
onDataViewsSort,
onIntegrationsLoadMore,
onIntegrationsReload,
onIntegrationsSearch,
onIntegrationsSort,
onIntegrationsStreamsSearch,
onIntegrationsStreamsSort,
onSelectionChange,
onUncategorizedSearch,
onUncategorizedSort,
onUncategorizedReload,
}: DataSourceSelectorStateMachineDependencies) => {
const dataSourceSelectorStateService = useInterpret(() =>
createDataSourceSelectorStateMachine({
initialContext,
onDataViewsSearch,
onDataViewsFilter,
onDataViewsSort,
onIntegrationsLoadMore,
onIntegrationsReload,
onIntegrationsSearch,
onIntegrationsSort,
onIntegrationsStreamsSearch,
onIntegrationsStreamsSort,
onSelectionChange,
onUncategorizedSearch,
onUncategorizedSort,
onUncategorizedReload,
})
);
const isOpen = useSelector(dataSourceSelectorStateService, (state) =>
state.matches('popover.open')
);
const panelId = useSelector(dataSourceSelectorStateService, (state) => state.context.panelId);
const search = useSelector(dataSourceSelectorStateService, (state) => state.context.search);
const dataViewsFilter = useSelector(
dataSourceSelectorStateService,
(state) => state.context.dataViewsFilter
);
const selection = useSelector(dataSourceSelectorStateService, (state) => state.context.selection);
const tabId = useSelector(dataSourceSelectorStateService, (state) => state.context.tabId);
const switchToIntegrationsTab = useCallback(
() => dataSourceSelectorStateService.send({ type: 'SWITCH_TO_INTEGRATIONS_TAB' }),
[dataSourceSelectorStateService]
);
const switchToUncategorizedTab = useCallback(
() => dataSourceSelectorStateService.send({ type: 'SWITCH_TO_UNCATEGORIZED_TAB' }),
[dataSourceSelectorStateService]
);
const switchToDataViewsTab = useCallback(
() => dataSourceSelectorStateService.send({ type: 'SWITCH_TO_DATA_VIEWS_TAB' }),
[dataSourceSelectorStateService]
);
const changePanel = useCallback<ChangePanelHandler>(
(panelDetails) =>
dataSourceSelectorStateService.send({
type: 'CHANGE_PANEL',
panelId: panelDetails.panelId as PanelId,
}),
[dataSourceSelectorStateService]
);
const scrollToIntegrationsBottom = useCallback(
() => dataSourceSelectorStateService.send({ type: 'SCROLL_TO_INTEGRATIONS_BOTTOM' }),
[dataSourceSelectorStateService]
);
const searchByName = useCallback<DataSourceSelectorSearchHandler>(
(params) => dataSourceSelectorStateService.send({ type: 'SEARCH_BY_NAME', search: params }),
[dataSourceSelectorStateService]
);
const filterByType = useCallback<DataViewFilterHandler>(
(params) => dataSourceSelectorStateService.send({ type: 'FILTER_BY_TYPE', filter: params }),
[dataSourceSelectorStateService]
);
const selectAllLogs = useCallback(
() => dataSourceSelectorStateService.send({ type: 'SELECT_ALL_LOGS' }),
[dataSourceSelectorStateService]
);
const selectDataset = useCallback<DatasetSelectionHandler>(
(dataset) =>
dataSourceSelectorStateService.send({ type: 'SELECT_DATASET', selection: dataset }),
[dataSourceSelectorStateService]
);
const selectDataView = useCallback<DataViewSelectionHandler>(
(dataViewDescriptor) =>
dataSourceSelectorStateService.send({
type: 'SELECT_DATA_VIEW',
selection: dataViewDescriptor,
}),
[dataSourceSelectorStateService]
);
const sortByOrder = useCallback<DataSourceSelectorSearchHandler>(
(params) => dataSourceSelectorStateService.send({ type: 'SORT_BY_ORDER', search: params }),
[dataSourceSelectorStateService]
);
const closePopover = useCallback(
() => dataSourceSelectorStateService.send({ type: 'CLOSE' }),
[dataSourceSelectorStateService]
);
const togglePopover = useCallback(
() => dataSourceSelectorStateService.send({ type: 'TOGGLE' }),
[dataSourceSelectorStateService]
);
return {
// Data
panelId,
search,
dataViewsFilter,
selection,
tabId,
// Flags
isOpen,
isAllMode: isAllDatasetSelection(selection),
// Actions
changePanel,
closePopover,
scrollToIntegrationsBottom,
searchByName,
filterByType,
selectAllLogs,
selectDataset,
selectDataView,
sortByOrder,
switchToIntegrationsTab,
switchToUncategorizedTab,
switchToDataViewsTab,
togglePopover,
};
};

View file

@ -1,49 +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 { EuiButtonEmpty } from '@elastic/eui';
import {
OBSERVABILITY_ONBOARDING_LOCATOR,
ObservabilityOnboardingLocatorParams,
} from '@kbn/deeplinks-observability';
import { getRouterLinkProps } from '@kbn/router-utils';
import React from 'react';
import { addDataLabel } from '../constants';
import { useKibanaContextForPlugin } from '../../../utils/use_kibana';
export const AddDataButton: React.FunctionComponent<{}> = ({}) => {
const {
services: {
share: { url: urlService },
},
} = useKibanaContextForPlugin();
const locator = urlService.locators.get<ObservabilityOnboardingLocatorParams>(
OBSERVABILITY_ONBOARDING_LOCATOR
);
const onboardingUrl = locator?.useUrl({});
const navigateToOnboarding = () => {
locator?.navigate({});
};
const onboardingLinkProps = getRouterLinkProps({
href: onboardingUrl,
onClick: navigateToOnboarding,
});
return (
<EuiButtonEmpty
data-test-subj="logsExplorerAddDataButtonButton"
{...onboardingLinkProps}
iconType="plusInCircleFilled"
size="xs"
>
{addDataLabel}
</EuiButtonEmpty>
);
};

View file

@ -1,117 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState } from 'react';
import {
EuiButtonEmpty,
EuiContextMenu,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DataViewFilterHandler } from '../types';
import { DataViewsFilterParams } from '../../../state_machines/data_views';
import {
allDataViewTypesLabel,
logsDataViewTypeLabel,
selectDataViewTypeLabel,
} from '../constants';
interface DataViewFilterProps {
onFilter: DataViewFilterHandler;
count: number;
filter: DataViewsFilterParams;
}
const logsDataViewType = 'logs';
function getSelectedFilterLabel(dataType: DataViewsFilterParams['dataType']) {
const availableFilters = {
[logsDataViewType]: logsDataViewTypeLabel,
};
return !dataType ? allDataViewTypesLabel : availableFilters[dataType];
}
export const DataViewsFilter = ({ count, filter, onFilter }: DataViewFilterProps) => {
const [isPopoverOpen, setPopover] = useState(false);
const closeTypePopover = () => {
setPopover(false);
};
const togglePopover = () => {
setPopover(!isPopoverOpen);
};
const createSelectTypeFilter = (dataType: DataViewsFilterParams['dataType']) => {
return () => {
onFilter({ dataType });
closeTypePopover();
};
};
return (
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="xs">
{i18n.translate('xpack.logsExplorer.dataSourceSelector.dataViewCount', {
defaultMessage: '{count, plural, one {# data view} other {# data views}}',
values: { count },
})}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={
<EuiButtonEmpty
data-test-subj="logsExplorerDataSourceSelectorDataViewTypeButton"
iconType="arrowDown"
iconSide="right"
size="xs"
onClick={togglePopover}
>
{getSelectedFilterLabel(filter.dataType)}
</EuiButtonEmpty>
}
isOpen={isPopoverOpen}
closePopover={closeTypePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenu
size="s"
initialPanelId={0}
panels={[
{
id: 0,
width: 'auto',
title: selectDataViewTypeLabel,
items: [
{
'data-test-subj': 'logsExplorerDataSourceSelectorDataViewTypeAll',
icon: !filter.dataType ? 'check' : 'empty',
name: allDataViewTypesLabel,
onClick: createSelectTypeFilter(undefined),
},
{
'data-test-subj': 'logsExplorerDataSourceSelectorDataViewTypeLogs',
icon: filter.dataType === logsDataViewType ? 'check' : 'empty',
name: logsDataViewTypeLabel,
onClick: createSelectTypeFilter(logsDataViewType),
},
],
},
]}
/>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -1,40 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiIcon, EuiToolTip, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor';
import { openDiscoverLabel } from '../constants';
interface DataViewMenuItemProps {
dataView: DataViewDescriptor;
isAvailable: boolean;
}
export const DataViewMenuItem = ({ dataView, isAvailable }: DataViewMenuItemProps) => {
const { euiTheme } = useEuiTheme();
if (isAvailable) {
return <span>{dataView.name}</span>;
}
return (
<>
<span
css={css`
margin-right: ${euiTheme.size.s};
`}
>
{dataView.name}
</span>
<EuiToolTip content={openDiscoverLabel}>
<EuiIcon type="popout" color="subdued" />
</EuiToolTip>
</>
);
};

View file

@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiPanel, EuiSkeletonText } from '@elastic/eui';
import { uncategorizedLabel } from '../constants';
export const DatasetSkeleton = () => (
<EuiPanel data-test-subj="dataSourceSelectorSkeleton">
<EuiSkeletonText lines={7} isLoading contentAriaLabel={uncategorizedLabel} />
</EuiPanel>
);

View file

@ -1,89 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiButton, EuiEmptyPrompt, EuiText, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor';
import { ReloadDatasets } from '../../../hooks/use_datasets';
import { errorLabel, noDataRetryLabel } from '../constants';
import type { Dataset, Integration } from '../../../../common/datasets';
import { DatasetSkeleton } from './datasets_skeleton';
export interface ListStatusProps {
data: Dataset[] | Integration[] | DataViewDescriptor[] | null;
description: string;
error: Error | null;
isLoading: boolean;
onRetry: ReloadDatasets;
title: string;
}
export const ListStatus = ({
data,
description,
error,
isLoading,
onRetry,
title,
}: ListStatusProps) => {
const isEmpty = data == null || data.length <= 0;
const hasError = error !== null;
if (isLoading) {
return <DatasetSkeleton />;
}
if (hasError) {
return (
<EuiEmptyPrompt
data-test-subj="dataSourceSelectorListStatusErrorPrompt"
iconType="warning"
iconColor="danger"
paddingSize="m"
title={<h2>{title}</h2>}
titleSize="s"
body={
<FormattedMessage
id="xpack.logsExplorer.dataSourceSelector.noDataError"
defaultMessage="An {error} occurred while getting your data. Please retry."
values={{
error: (
<EuiToolTip content={error.message}>
<EuiText color="danger">{errorLabel}</EuiText>
</EuiToolTip>
),
}}
/>
}
actions={[
<EuiButton data-test-subj="logsExplorerListStatusButton" onClick={onRetry}>
{noDataRetryLabel}
</EuiButton>,
]}
/>
);
}
if (isEmpty) {
return (
<EuiEmptyPrompt
data-test-subj="dataSourceSelectorListStatusEmptyPrompt"
iconType="search"
paddingSize="m"
title={<h2>{title}</h2>}
titleSize="s"
body={<p>{description}</p>}
/>
);
}
return null;
};
// eslint-disable-next-line import/no-default-export
export default ListStatus;

View file

@ -1,78 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiButtonGroup, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { SortOrder } from '../../../../common/latest';
import { DATA_SOURCE_SELECTOR_WIDTH, sortOptions, sortOrdersLabel } from '../constants';
import { DataSourceSelectorSearchHandler, DataSourceSelectorSearchParams } from '../types';
interface SearchControlsProps {
isLoading: boolean;
onSearch: DataSourceSelectorSearchHandler;
onSort: DataSourceSelectorSearchHandler;
search: DataSourceSelectorSearchParams;
filterComponent?: React.ReactNode;
}
export const SearchControls = ({
search,
onSearch,
onSort,
isLoading,
filterComponent,
}: SearchControlsProps) => {
const handleNameChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
const newSearch = {
...search,
name: event.target.value,
};
onSearch(newSearch);
};
const handleSortChange = (id: string) => {
const newSearch = { ...search, sortOrder: id as DataSourceSelectorSearchParams['sortOrder'] };
onSort(newSearch);
};
return (
<EuiPanel
paddingSize="s"
hasShadow={false}
css={{ width: DATA_SOURCE_SELECTOR_WIDTH }}
data-test-subj="dataSourceSelectorSearchControls"
>
<EuiFlexGroup gutterSize="s" direction="column">
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs" responsive={false}>
<EuiFlexItem>
<EuiFieldSearch
data-test-subj="logsExplorerSearchControlsFieldSearch"
compressed
incremental
value={search.name}
onChange={handleNameChange}
isLoading={isLoading}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonGroup
isIconOnly
buttonSize="compressed"
options={sortOptions}
legend={sortOrdersLabel}
idSelected={search.sortOrder as SortOrder}
onChange={handleSortChange}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{filterComponent && <EuiFlexItem>{filterComponent}</EuiFlexItem>}
</EuiFlexGroup>
</EuiPanel>
);
};

View file

@ -1,65 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import {
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiFlexGroupProps,
} from '@elastic/eui';
import { getRouterLinkProps } from '@kbn/router-utils';
import { AllDatasetSelection } from '../../../../common';
import { DiscoverEsqlUrlProps } from '../../../hooks/use_esql';
import { createAllLogsItem } from '../utils';
import { showAllLogsLabel, tryEsql } from '../constants';
interface ShowAllLogsProps {
isSelected: boolean;
onClick(): void;
allSelection: AllDatasetSelection;
}
export const SelectorFooter = (props: EuiFlexGroupProps) => {
return (
<EuiPanel paddingSize="s" hasShadow={false} data-test-subj="dataSourceSelectorSearchFooter">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" {...props} />
</EuiPanel>
);
};
export const ShowAllLogsButton = ({ isSelected, onClick, allSelection }: ShowAllLogsProps) => {
const allLogs = createAllLogsItem(allSelection);
return (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj={allLogs['data-test-subj']}
onClick={onClick}
size="s"
iconType={isSelected ? 'check' : allLogs.iconType}
flush="left"
>
{showAllLogsLabel}
</EuiButtonEmpty>
</EuiFlexItem>
);
};
export const ESQLButton = (props: DiscoverEsqlUrlProps) => {
const linkProps = getRouterLinkProps(props);
return (
<EuiFlexItem grow={false}>
<EuiButton {...linkProps} color="text" size="s" data-test-subj="esqlLink">
{tryEsql}
</EuiButton>
</EuiFlexItem>
);
};

View file

@ -1,112 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import {
EuiButton,
EuiIcon,
EuiPanel,
EuiPopover,
EuiPopoverProps,
useIsWithinBreakpoints,
} from '@elastic/eui';
import { PackageIcon } from '@kbn/fleet-plugin/public';
import {
DatasetSelection,
DataSourceSelection,
DataViewSelection,
isDataViewSelection,
} from '../../../../common/data_source_selection';
import { DATA_SOURCE_SELECTOR_WIDTH, POPOVER_ID } from '../constants';
import { getPopoverButtonStyles } from '../utils';
const panelStyle = { width: DATA_SOURCE_SELECTOR_WIDTH };
interface SelectorPopoverProps extends Omit<EuiPopoverProps, 'button'> {
children: React.ReactNode;
onClick: () => void;
selection: DataSourceSelection;
}
export const SelectorPopover = ({
children,
onClick,
selection,
...props
}: SelectorPopoverProps) => {
const isMobile = useIsWithinBreakpoints(['xs', 's']);
const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile });
return (
<EuiPopover
id={POPOVER_ID}
data-test-subj="dataSourceSelectorPopover"
anchorPosition={isMobile ? 'downCenter' : 'downLeft'}
button={
<EuiButton
css={buttonStyles}
iconType="arrowDown"
iconSide="right"
onClick={onClick}
fullWidth={isMobile}
data-test-subj="dataSourceSelectorPopoverButton"
>
{isDataViewSelection(selection) ? (
<DataViewPopoverContent dataViewSelection={selection} />
) : (
<DatasetPopoverContent datasetSelection={selection} />
)}
</EuiButton>
}
panelPaddingSize="none"
buffer={8}
{...(isMobile && { display: 'block' })}
{...props}
>
<EuiPanel
paddingSize="none"
hasShadow={false}
css={panelStyle}
data-test-subj="dataSourceSelectorContent"
>
{children}
</EuiPanel>
</EuiPopover>
);
};
const DataViewPopoverContent = ({
dataViewSelection,
}: {
dataViewSelection: DataViewSelection;
}) => {
const { name } = dataViewSelection.selection.dataView;
return <span className="eui-textTruncate">{name}</span>;
};
const DatasetPopoverContent = ({ datasetSelection }: { datasetSelection: DatasetSelection }) => {
const { iconType, parentIntegration } = datasetSelection.selection.dataset;
const title = datasetSelection.selection.dataset.getFullTitle();
const hasIntegration = typeof parentIntegration === 'object';
return (
<>
{iconType ? (
<EuiIcon type={iconType} />
) : hasIntegration ? (
<PackageIcon
packageName={parentIntegration.name ?? ''}
version={parentIntegration.version ?? '1.0.0'}
icons={parentIntegration.icons}
size="m"
tryApi
/>
) : null}
<span className="eui-textTruncate">{title}</span>
</>
);
};

View file

@ -1,121 +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 { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu';
import type {
DataSourceSelectionChangeHandler,
DataSourceSelection,
AllDatasetSelection,
} from '../../../common/data_source_selection';
import { SortOrder } from '../../../common/latest';
import { Dataset, Integration, IntegrationId } from '../../../common/datasets';
import { DataViewDescriptor } from '../../../common/data_views/models/data_view_descriptor';
import { LoadDatasets, ReloadDatasets, SearchDatasets } from '../../hooks/use_datasets';
import {
LoadMoreIntegrations,
ReloadIntegrations,
SearchIntegrations,
} from '../../hooks/use_integrations';
import {
DATA_VIEWS_TAB_ID,
INTEGRATIONS_PANEL_ID,
INTEGRATIONS_TAB_ID,
UNCATEGORIZED_TAB_ID,
} from './constants';
import {
FilterDataViews,
IsDataViewAllowed,
IsDataViewAvailable,
LoadDataViews,
ReloadDataViews,
SearchDataViews,
} from '../../hooks/use_data_views';
import { DiscoverEsqlUrlProps } from '../../hooks/use_esql';
import { DataViewsFilterParams } from '../../state_machines/data_views';
export interface DataSourceSelectorProps {
/* The generic data stream list */
datasets: Dataset[] | null;
/* Class to represent the current "All logs" selection */
allSelection: AllDatasetSelection;
/* Any error occurred to show when the user preview the generic data streams */
datasetsError: Error | null;
/* The current selection instance */
dataSourceSelection: DataSourceSelection;
/* The available data views list */
dataViews: DataViewDescriptor[] | null;
/* The total number of data views */
dataViewCount: number;
/* Any error occurred to show when the user preview the data views */
dataViewsError: Error | null;
/* url props to navigate to discover ES|QL */
discoverEsqlUrlProps: DiscoverEsqlUrlProps;
/* The integrations list, each integration includes its data streams */
integrations: Integration[] | null;
/* Any error occurred to show when the user preview the integrations */
integrationsError: Error | null;
/* Flags for loading/searching integrations, data streams or data views*/
isLoadingDataViews: boolean;
isLoadingIntegrations: boolean;
isLoadingUncategorized: boolean;
isSearchingIntegrations: boolean;
/* Flag for determining whether ESQL is enabled or not */
isEsqlEnabled: boolean;
/* Used against a data view to assert if its allowed on the selector */
isDataViewAllowed: IsDataViewAllowed;
/* Used against a data view to assert its availability */
isDataViewAvailable: IsDataViewAvailable;
/* Triggered when retrying to load the data views */
onDataViewsReload: ReloadDataViews;
/* Triggered when the data views tab is selected */
onDataViewsTabClick: LoadDataViews;
/* Triggered when we reach the bottom of the integration list and want to load more */
onIntegrationsLoadMore: LoadMoreIntegrations;
/* Triggered when the user reload the list after an error */
onIntegrationsReload: ReloadIntegrations;
/* Triggered when a search or sorting is performed */
onDataViewsFilter: FilterDataViews;
onDataViewsSearch: SearchDataViews;
onDataViewsSort: SearchDataViews;
onIntegrationsSearch: SearchIntegrations;
onIntegrationsSort: SearchIntegrations;
onIntegrationsStreamsSearch: SearchIntegrations;
onIntegrationsStreamsSort: SearchIntegrations;
onUncategorizedSearch: SearchDatasets;
onUncategorizedSort: SearchDatasets;
/* Triggered when retrying to load the data streams */
onUncategorizedReload: ReloadDatasets;
/* Triggered when the uncategorized tab is selected */
onUncategorizedTabClick: LoadDatasets;
/* Triggered when the selection is updated */
onSelectionChange: DataSourceSelectionChangeHandler;
}
export type PanelId = typeof INTEGRATIONS_PANEL_ID | IntegrationId;
export type TabId =
| typeof INTEGRATIONS_TAB_ID
| typeof UNCATEGORIZED_TAB_ID
| typeof DATA_VIEWS_TAB_ID;
export interface SearchParams {
integrationId?: PanelId;
name: string;
sortOrder: SortOrder;
}
export type DataSourceSelectorSearchParams = Pick<SearchParams, 'name' | 'sortOrder'>;
export type DataSourceSelectorSearchHandler = (params: DataSourceSelectorSearchParams) => void;
export type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId }) => void;
export type DatasetSelectionHandler = (dataset: Dataset) => void;
export type DataViewSelectionHandler = (dataView: DataViewDescriptor) => void;
export type DataViewFilterHandler = (params: DataViewsFilterParams) => void;

View file

@ -1,136 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { RefCallback } from 'react';
import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
import { PackageIcon } from '@kbn/fleet-plugin/public';
import { AllDatasetSelection } from '../../../common';
import { Integration } from '../../../common/datasets';
import {
DATA_SOURCE_SELECTOR_WIDTH,
noDatasetsDescriptionLabel,
noDatasetsLabel,
noDataViewsDescriptionLabel,
noDataViewsLabel,
noIntegrationsDescriptionLabel,
noIntegrationsLabel,
} from './constants';
import { DatasetSelectionHandler } from './types';
import ListStatus, { ListStatusProps } from './sub_components/list_status';
export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({
maxWidth: fullWidth ? undefined : DATA_SOURCE_SELECTOR_WIDTH,
});
interface IntegrationsTreeParams {
integrations: Integration[];
onDatasetSelected: DatasetSelectionHandler;
spyRef: RefCallback<HTMLButtonElement>;
}
interface IntegrationsTree {
items: EuiContextMenuPanelItemDescriptor[];
panels: EuiContextMenuPanelDescriptor[];
}
/**
* The `EuiContextMenu` component receives a list of panels,
* each one with a pointer id which is used as a reference for the items to know
* what panel they refer to.
* This helper function, starting from a list of integrations,
* generate the necessary item entries for each integration,
* and also create a related panel that render the list of data streams for the integration.
*/
export const buildIntegrationsTree = ({
integrations,
onDatasetSelected,
spyRef,
}: IntegrationsTreeParams) => {
return integrations.reduce(
(integrationsTree: IntegrationsTree, integration, pos) => {
const { name, title, version, datasets, icons } = integration;
const isLastIntegration = pos === integrations.length - 1;
integrationsTree.items.push({
name: title,
icon: <PackageIcon packageName={name} version={version} size="m" icons={icons} tryApi />,
'data-test-subj': integration.id,
panel: integration.id,
...(isLastIntegration && { buttonRef: spyRef }),
});
integrationsTree.panels.push({
id: integration.id,
title,
width: DATA_SOURCE_SELECTOR_WIDTH,
items: datasets.map((dataset) => ({
name: dataset.title,
onClick: () => onDatasetSelected(dataset),
})),
});
return integrationsTree;
},
{ items: [], panels: [] }
);
};
export const createAllLogsItem = (allSelection: AllDatasetSelection) => {
return {
'data-test-subj': 'dataSourceSelectorShowAllLogs',
iconType: allSelection.selection.dataset.iconType,
name: allSelection.selection.dataset.title,
};
};
export const createIntegrationStatusItem = (
props: Omit<ListStatusProps, 'description' | 'title'>
) => {
return {
disabled: true,
name: (
<ListStatus
key="integrationStatusItem"
description={noIntegrationsDescriptionLabel}
title={noIntegrationsLabel}
{...props}
/>
),
};
};
export const createUncategorizedStatusItem = (
props: Omit<ListStatusProps, 'description' | 'title'>
) => {
return {
disabled: true,
name: (
<ListStatus
key="uncategorizedStatusItem"
description={noDatasetsDescriptionLabel}
title={noDatasetsLabel}
{...props}
/>
),
};
};
export const createDataViewsStatusItem = (
props: Omit<ListStatusProps, 'description' | 'title'>
) => {
return {
disabled: true,
name: (
<ListStatus
key="dataViewsStatusItem"
description={noDataViewsDescriptionLabel}
title={noDataViewsLabel}
{...props}
/>
),
};
};

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './logs_explorer';

View file

@ -1,47 +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 { ScopedHistory } from '@kbn/core-application-browser';
import type { CoreStart } from '@kbn/core/public';
import React, { useMemo } from 'react';
import type { LogsExplorerController } from '../../controller';
import { createLogsExplorerProfileCustomizations } from '../../customizations/logs_explorer_profile';
import { LogsExplorerStartDeps } from '../../types';
export interface CreateLogsExplorerArgs {
core: CoreStart;
plugins: LogsExplorerStartDeps;
}
export interface LogsExplorerProps {
scopedHistory: ScopedHistory;
controller: LogsExplorerController;
}
export const createLogsExplorer = ({ core, plugins }: CreateLogsExplorerArgs) => {
const {
discover: { DiscoverContainer },
} = plugins;
return ({ scopedHistory, controller }: LogsExplorerProps) => {
const logsExplorerCustomizations = useMemo(
() => [createLogsExplorerProfileCustomizations({ controller, core, plugins })],
[controller]
);
const { urlStateStorage, ...overrideServices } = controller.discoverServices;
return (
<DiscoverContainer
customizationCallbacks={logsExplorerCustomizations}
overrideServices={overrideServices}
scopedHistory={scopedHistory}
stateStorageContainer={urlStateStorage}
/>
);
};
};

View file

@ -1,44 +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 { EuiFlexGroup, EuiFlexItem, EuiText, EuiToken, useEuiTheme } from '@elastic/eui';
import React from 'react';
import { css } from '@emotion/react';
export const FieldWithToken = ({
field,
iconType = 'tokenKeyword',
}: {
field: string;
iconType?: string;
}) => {
const { euiTheme } = useEuiTheme();
return (
<div
css={css`
margin-bottom: ${euiTheme.size.xs};
`}
>
<EuiFlexGroup
responsive={false}
alignItems="center"
justifyContent="flexStart"
gutterSize="xs"
>
<EuiFlexItem grow={false}>
<EuiToken iconType={iconType} size="s" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
<strong>{field}</strong>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</div>
);
};

View file

@ -1,78 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import { EuiIcon } from '@elastic/eui';
import ColumnHeaderTruncateContainer from '@kbn/unified-data-table/src/components/column_header_truncate_container';
import { EuiPopover, EuiPopoverTitle } from '@elastic/eui';
export const TooltipButton = ({
children,
popoverTitle,
displayText,
headerRowHeight,
iconType = 'questionInCircle',
}: {
children: React.ReactChild;
popoverTitle: string;
displayText?: string;
headerRowHeight?: number;
iconType?: string;
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const leaveTimer = useRef<NodeJS.Timeout | null>(null);
const clearTimer = useMemo(
() => () => {
if (leaveTimer.current) {
clearTimeout(leaveTimer.current);
}
},
[]
);
const onMouseEnter = useCallback(() => {
clearTimer();
setIsPopoverOpen(true);
}, [clearTimer]);
const onMouseLeave = useCallback(() => {
leaveTimer.current = setTimeout(() => setIsPopoverOpen(false), 100);
}, []);
useEffect(() => {
return () => {
clearTimer();
};
}, [clearTimer]);
return (
<ColumnHeaderTruncateContainer headerRowHeight={headerRowHeight}>
{displayText}{' '}
<EuiPopover
button={
<EuiIcon
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onFocus={onMouseEnter}
onBlur={onMouseLeave}
type={iconType}
tabIndex={0}
/>
}
isOpen={isPopoverOpen}
anchorPosition="upCenter"
panelPaddingSize="s"
ownFocus={false}
>
<EuiPopoverTitle>{popoverTitle}</EuiPopoverTitle>
{children}
</EuiPopover>
</ColumnHeaderTruncateContainer>
);
};

View file

@ -1,115 +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 { CoreStart } from '@kbn/core/public';
import { getDevToolsOptions } from '@kbn/xstate-utils';
import equal from 'fast-deep-equal';
import { distinctUntilChanged, from, map, shareReplay, Subject } from 'rxjs';
import { interpret } from 'xstate';
import { AllDatasetSelection } from '../../common';
import { DatasetsService } from '../services/datasets';
import {
createLogsExplorerControllerStateMachine,
DEFAULT_CONTEXT,
} from '../state_machines/logs_explorer_controller';
import { LogsExplorerStartDeps } from '../types';
import { LogsExplorerCustomizations } from '../customizations/types';
import { createDataServiceProxy } from './custom_data_service';
import { createUiSettingsServiceProxy } from './custom_ui_settings_service';
import {
createDiscoverMemoryHistory,
createMemoryUrlStateStorage,
} from './custom_url_state_storage';
import { getContextFromPublicState, getPublicStateFromContext } from './public_state';
import type {
LogsExplorerController,
LogsExplorerDiscoverServices,
LogsExplorerPublicEvent,
LogsExplorerPublicStateUpdate,
} from './types';
interface Dependencies {
core: CoreStart;
plugins: LogsExplorerStartDeps;
}
type InitialState = LogsExplorerPublicStateUpdate & { allSelection?: AllDatasetSelection };
export const createLogsExplorerControllerFactory =
({ core, plugins }: Dependencies) =>
async ({
customizations = {},
initialState,
}: {
customizations?: LogsExplorerCustomizations;
initialState?: InitialState;
}): Promise<LogsExplorerController> => {
const { data, dataViews } = plugins;
const datasetsClient = new DatasetsService().start({
http: core.http,
}).client;
const customMemoryHistory = createDiscoverMemoryHistory();
const customMemoryUrlStateStorage = createMemoryUrlStateStorage(customMemoryHistory);
const customUiSettings = createUiSettingsServiceProxy(core.uiSettings);
const customData = createDataServiceProxy({
data,
http: core.http,
uiSettings: customUiSettings,
});
const discoverServices: LogsExplorerDiscoverServices = {
data: customData,
history: customMemoryHistory,
uiSettings: customUiSettings,
filterManager: customData.query.filterManager,
timefilter: customData.query.timefilter.timefilter,
urlStateStorage: customMemoryUrlStateStorage,
};
const allSelection = initialState?.allSelection ?? DEFAULT_CONTEXT.allSelection;
const initialContext = getContextFromPublicState(initialState ?? {}, allSelection);
const publicEvents$ = new Subject<LogsExplorerPublicEvent>();
const machine = createLogsExplorerControllerStateMachine({
datasetsClient,
dataViews,
events: customizations.events,
initialContext: {
...initialContext,
allSelection,
},
query: discoverServices.data.query,
toasts: core.notifications.toasts,
uiSettings: customUiSettings,
publicEvents$,
});
const service = interpret(machine, {
devTools: getDevToolsOptions(),
});
const logsExplorerState$ = from(service).pipe(
map(({ context }) => getPublicStateFromContext(context)),
distinctUntilChanged(equal),
shareReplay(1)
);
return {
actions: {},
customizations,
datasetsClient,
discoverServices,
event$: publicEvents$,
service,
state$: logsExplorerState$,
stateMachine: machine,
};
};
export type CreateLogsExplorerControllerFactory = typeof createLogsExplorerControllerFactory;
export type CreateLogsExplorerController = ReturnType<typeof createLogsExplorerControllerFactory>;

View file

@ -1,63 +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 { HttpStart } from '@kbn/core-http-browser';
import { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
import { DataPublicPluginStart, NowProvider, QueryService } from '@kbn/data-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { createPropertyGetProxy } from '../utils/proxies';
/**
* Create proxy for the data service, in which session service enablement calls
* are no-ops.
*/
export const createDataServiceProxy = ({
data,
http,
uiSettings,
}: {
data: DataPublicPluginStart;
http: HttpStart;
uiSettings: IUiSettingsClient;
}) => {
/**
* search session
*/
const noOpEnableStorage = () => {};
const sessionServiceProxy = createPropertyGetProxy(data.search.session, {
enableStorage: () => noOpEnableStorage,
});
const searchServiceProxy = createPropertyGetProxy(data.search, {
session: () => sessionServiceProxy,
});
/**
* query
*/
const customStorage = new Storage(localStorage);
const customQueryService = new QueryService();
customQueryService.setup({
nowProvider: new NowProvider(),
storage: customStorage,
uiSettings,
});
const customQuery = customQueryService.start({
http,
storage: customStorage,
uiSettings,
});
/**
* combined
*/
return createPropertyGetProxy(data, {
query: () => customQuery,
search: () => searchServiceProxy,
});
};

View file

@ -1,33 +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 { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
import { HIDE_ANNOUNCEMENTS, MODIFY_COLUMNS_ON_SWITCH } from '@kbn/discover-utils';
import { createPropertyGetProxy } from '../utils/proxies';
/**
* Create proxy for the uiSettings service, in which settings preferences are overwritten
* with custom values
*/
export const createUiSettingsServiceProxy = (uiSettings: IUiSettingsClient) => {
const overrides: Record<string, any> = {
[HIDE_ANNOUNCEMENTS]: true,
[MODIFY_COLUMNS_ON_SWITCH]: false,
};
return createPropertyGetProxy(uiSettings, {
get:
() =>
(key, ...args) => {
if (key in overrides) {
return overrides[key];
}
return uiSettings.get(key, ...args);
},
});
};

View file

@ -1,32 +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 { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
import { createMemoryHistory } from 'history';
import { LogsExplorerDiscoverServices } from './types';
type DiscoverHistory = LogsExplorerDiscoverServices['history'];
/**
* Create a MemoryHistory instance. It is initialized with an application state
* object, because Discover radically resets too much when the URL is "empty".
*/
export const createDiscoverMemoryHistory = (): DiscoverHistory =>
createMemoryHistory({
initialEntries: [{ search: `?_a=()` }],
});
/**
* Create a url state storage that's not connected to the real browser location
* to isolate the Discover component from these side-effects.
*/
export const createMemoryUrlStateStorage = (memoryHistory: DiscoverHistory) =>
createKbnUrlStateStorage({
history: memoryHistory,
useHash: false,
useHashQuery: false,
});

View file

@ -1,11 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from '../customizations/types';
export * from './create_controller';
export * from './provider';
export * from './types';

View file

@ -1,15 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CreateLogsExplorerControllerFactory } from './create_controller';
export const createLogsExplorerControllerLazyFactory: CreateLogsExplorerControllerFactory =
(dependencies) => async (args) => {
const { createLogsExplorerControllerFactory } = await import('./create_controller');
return createLogsExplorerControllerFactory(dependencies)(args);
};

View file

@ -1,15 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import createContainer from 'constate';
import type { LogsExplorerController } from './types';
const useLogsExplorerController = ({ controller }: { controller: LogsExplorerController }) =>
controller;
export const [LogsExplorerControllerProvider, useLogsExplorerControllerContext] =
createContainer(useLogsExplorerController);

View file

@ -1,133 +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 {
AllDatasetSelection,
availableControlsPanels,
controlPanelConfigs,
ControlPanels,
hydrateDataSourceSelection,
} from '../../common';
import {
DEFAULT_CONTEXT,
LogsExplorerControllerContext,
} from '../state_machines/logs_explorer_controller';
import {
LogsExplorerPublicState,
LogsExplorerPublicStateUpdate,
OptionsListControl,
} from './types';
export const getPublicStateFromContext = (
context: LogsExplorerControllerContext
): LogsExplorerPublicState => {
return {
chart: context.chart,
dataSourceSelection: context.dataSourceSelection.toPlainSelection(),
grid: context.grid,
filters: context.filters,
query: context.query,
refreshInterval: context.refreshInterval,
time: context.time,
controls: getPublicControlsStateFromControlPanels(context.controlPanels),
};
};
export const getContextFromPublicState = (
publicState: LogsExplorerPublicStateUpdate,
allSelection: AllDatasetSelection
): LogsExplorerControllerContext => ({
...DEFAULT_CONTEXT,
chart: {
...DEFAULT_CONTEXT.chart,
...publicState.chart,
},
controlPanels: getControlPanelsFromPublicControlsState(publicState.controls),
dataSourceSelection:
publicState.dataSourceSelection != null
? hydrateDataSourceSelection(publicState.dataSourceSelection, allSelection)
: DEFAULT_CONTEXT.dataSourceSelection,
grid: {
...DEFAULT_CONTEXT.grid,
...publicState.grid,
rows: {
...DEFAULT_CONTEXT.grid.rows,
...publicState.grid?.rows,
},
},
filters: publicState.filters ?? DEFAULT_CONTEXT.filters,
query: publicState.query ?? DEFAULT_CONTEXT.query,
refreshInterval: publicState.refreshInterval ?? DEFAULT_CONTEXT.refreshInterval,
time: publicState.time ?? DEFAULT_CONTEXT.time,
});
const getPublicControlsStateFromControlPanels = (
controlPanels: ControlPanels | undefined
): LogsExplorerPublicState['controls'] =>
controlPanels != null
? {
...(availableControlsPanels.NAMESPACE in controlPanels
? {
[availableControlsPanels.NAMESPACE]: getOptionsListPublicControlStateFromControlPanel(
controlPanels[availableControlsPanels.NAMESPACE]
),
}
: {}),
}
: {};
const getOptionsListPublicControlStateFromControlPanel = (
optionsListControlPanel: ControlPanels[string]
): OptionsListControl => ({
mode: optionsListControlPanel.exclude ? 'exclude' : 'include',
selection: optionsListControlPanel.existsSelected
? { type: 'exists' }
: {
type: 'options',
selectedOptions: optionsListControlPanel.selectedOptions ?? [],
},
});
const getControlPanelsFromPublicControlsState = (
publicControlsState: LogsExplorerPublicStateUpdate['controls']
): ControlPanels => {
if (publicControlsState == null) {
return {};
}
const namespacePublicControlState = publicControlsState[availableControlsPanels.NAMESPACE];
return {
...(namespacePublicControlState
? {
[availableControlsPanels.NAMESPACE]: getControlPanelFromOptionsListPublicControlState(
availableControlsPanels.NAMESPACE,
namespacePublicControlState
),
}
: {}),
};
};
const getControlPanelFromOptionsListPublicControlState = (
controlId: string,
publicControlState: OptionsListControl
): ControlPanels[string] => {
const defaultControlPanelConfig = controlPanelConfigs[controlId];
return {
...defaultControlPanelConfig,
exclude: publicControlState.mode === 'exclude',
...(publicControlState.selection.type === 'exists'
? {
existsSelected: true,
}
: {
selectedOptions: publicControlState.selection.selectedOptions,
}),
};
};

View file

@ -1,79 +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 { QueryState } from '@kbn/data-plugin/public';
import { DiscoverContainerProps } from '@kbn/discover-plugin/public';
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
import { Observable } from 'rxjs';
import {
availableControlsPanels,
DataSourceSelectionPlain,
DisplayOptions,
PartialDisplayOptions,
} from '../../common';
import { IDatasetsClient } from '../services/datasets';
import {
LogsExplorerControllerStateMachine,
LogsExplorerControllerStateService,
} from '../state_machines/logs_explorer_controller';
import { LogsExplorerCustomizations } from '../customizations/types';
export interface LogsExplorerController {
actions: {};
customizations: LogsExplorerCustomizations;
datasetsClient: IDatasetsClient;
discoverServices: LogsExplorerDiscoverServices;
event$: Observable<LogsExplorerPublicEvent>;
service: LogsExplorerControllerStateService;
state$: Observable<LogsExplorerPublicState>;
stateMachine: LogsExplorerControllerStateMachine;
}
export type LogsExplorerDiscoverServices = Pick<
Required<DiscoverContainerProps['overrideServices']>,
'data' | 'filterManager' | 'timefilter' | 'uiSettings' | 'history'
> & {
urlStateStorage: IKbnUrlStateStorage;
};
export interface OptionsListControlOption {
type: 'options';
selectedOptions: string[];
}
export interface OptionsListControlExists {
type: 'exists';
}
export interface OptionsListControl {
mode: 'include' | 'exclude';
selection: OptionsListControlOption | OptionsListControlExists;
}
export interface ControlOptions {
[availableControlsPanels.NAMESPACE]?: OptionsListControl;
}
// we might want to wrap this into an object that has a "state value" laster
export type LogsExplorerPublicState = QueryState &
DisplayOptions & {
controls: ControlOptions;
dataSourceSelection: DataSourceSelectionPlain;
};
export type LogsExplorerPublicStateUpdate = QueryState &
PartialDisplayOptions & {
controls?: ControlOptions;
dataSourceSelection?: DataSourceSelectionPlain;
};
export interface LogsExplorerPublicEvent {
type: 'LOGS_EXPLORER_DATA_RECEIVED';
payload: {
rowCount: number;
};
}

View file

@ -1,14 +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 { createDegradedDocsControl, createStacktraceControl } from '@kbn/discover-utils';
import { type UnifiedDataTableProps } from '@kbn/unified-data-table';
export const getRowAdditionalControlColumns =
(): UnifiedDataTableProps['rowAdditionalLeadingControls'] => {
return [createDegradedDocsControl(), createStacktraceControl()];
};

View file

@ -1,44 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { ControlGroupRenderer } from '@kbn/controls-plugin/public';
import { Query } from '@kbn/es-query';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { useControlPanels } from '../hooks/use_control_panels';
import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller';
const DATA_SOURCE_FILTERS_CUSTOMIZATION_ID = 'dataSourceFiltersCustomization';
interface CustomDataSourceFiltersProps {
logsExplorerControllerStateService: LogsExplorerControllerStateService;
data: DataPublicPluginStart;
}
const CustomDataSourceFilters = ({
logsExplorerControllerStateService,
data,
}: CustomDataSourceFiltersProps) => {
const { getInitialState, setControlGroupAPI, query, filters, timeRange } = useControlPanels(
logsExplorerControllerStateService,
data
);
return (
<div data-test-subj={DATA_SOURCE_FILTERS_CUSTOMIZATION_ID}>
<ControlGroupRenderer
onApiAvailable={setControlGroupAPI}
getCreationOptions={getInitialState}
query={query as Query}
filters={filters ?? []}
timeRange={timeRange}
/>
</div>
);
};
// eslint-disable-next-line import/no-default-export
export default CustomDataSourceFilters;

View file

@ -1,139 +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 { CoreStart } from '@kbn/core/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import React from 'react';
import { DataSourceSelector } from '../components/data_source_selector';
import { LogsExplorerController } from '../controller';
import { DatasetsProvider, useDatasetsContext } from '../hooks/use_datasets';
import { useDataSourceSelection } from '../hooks/use_data_source_selection';
import { DataViewsProvider, useDataViewsContext } from '../hooks/use_data_views';
import { useEsql } from '../hooks/use_esql';
import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations';
import { IDatasetsClient } from '../services/datasets';
import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller';
interface CustomDataSourceSelectorProps {
logsExplorerControllerStateService: LogsExplorerControllerStateService;
}
export const CustomDataSourceSelector = withProviders(({ logsExplorerControllerStateService }) => {
const { dataSourceSelection, handleDataSourceSelectionChange, allSelection } =
useDataSourceSelection(logsExplorerControllerStateService);
const {
error: integrationsError,
integrations,
isLoading: isLoadingIntegrations,
isSearching: isSearchingIntegrations,
loadMore,
reloadIntegrations,
searchIntegrations,
searchIntegrationsStreams,
sortIntegrations,
sortIntegrationsStreams,
} = useIntegrationsContext();
const {
datasets,
error: datasetsError,
isLoading: isLoadingUncategorized,
loadDatasets,
reloadDatasets,
searchDatasets,
sortDatasets,
} = useDatasetsContext();
const {
dataViews,
dataViewCount,
error: dataViewsError,
isLoading: isLoadingDataViews,
isDataViewAllowed,
isDataViewAvailable,
loadDataViews,
reloadDataViews,
searchDataViews,
filterDataViews,
sortDataViews,
} = useDataViewsContext();
const { isEsqlEnabled, discoverEsqlUrlProps } = useEsql({ dataSourceSelection });
return (
<DataSourceSelector
datasets={datasets}
dataSourceSelection={dataSourceSelection}
allSelection={allSelection}
datasetsError={datasetsError}
dataViews={dataViews}
dataViewCount={dataViewCount}
dataViewsError={dataViewsError}
discoverEsqlUrlProps={discoverEsqlUrlProps}
isDataViewAllowed={isDataViewAllowed}
isDataViewAvailable={isDataViewAvailable}
integrations={integrations}
integrationsError={integrationsError}
isEsqlEnabled={isEsqlEnabled}
isLoadingDataViews={isLoadingDataViews}
isLoadingIntegrations={isLoadingIntegrations}
isLoadingUncategorized={isLoadingUncategorized}
isSearchingIntegrations={isSearchingIntegrations}
onDataViewsReload={reloadDataViews}
onDataViewsSearch={searchDataViews}
onDataViewsFilter={filterDataViews}
onDataViewsSort={sortDataViews}
onDataViewsTabClick={loadDataViews}
onIntegrationsLoadMore={loadMore}
onIntegrationsReload={reloadIntegrations}
onIntegrationsSearch={searchIntegrations}
onIntegrationsSort={sortIntegrations}
onIntegrationsStreamsSearch={searchIntegrationsStreams}
onIntegrationsStreamsSort={sortIntegrationsStreams}
onSelectionChange={handleDataSourceSelectionChange}
onUncategorizedReload={reloadDatasets}
onUncategorizedSearch={searchDatasets}
onUncategorizedSort={sortDatasets}
onUncategorizedTabClick={loadDatasets}
/>
);
});
// eslint-disable-next-line import/no-default-export
export default CustomDataSourceSelector;
export type CustomDataSourceSelectorBuilderProps = CustomDataSourceSelectorProps & {
controller: LogsExplorerController;
core: CoreStart;
datasetsClient: IDatasetsClient;
dataViews: DataViewsPublicPluginStart;
};
function withProviders(Component: React.FunctionComponent<CustomDataSourceSelectorProps>) {
return function ComponentWithProviders({
controller,
core,
datasetsClient,
dataViews,
logsExplorerControllerStateService,
}: CustomDataSourceSelectorBuilderProps) {
return (
<IntegrationsProvider datasetsClient={datasetsClient}>
<DatasetsProvider datasetsClient={datasetsClient}>
<DataViewsProvider
core={core}
dataViewsService={dataViews}
events={controller.customizations?.events}
>
<Component logsExplorerControllerStateService={logsExplorerControllerStateService} />
</DataViewsProvider>
</DatasetsProvider>
</IntegrationsProvider>
);
};
}

View file

@ -1,41 +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 { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
export const createCustomSearchBar = ({
navigation,
data,
unifiedSearch,
}: {
data: DataPublicPluginStart;
navigation: NavigationPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
}) => {
const {
ui: { createTopNavWithCustomContext },
} = navigation;
const {
ui: { getCustomSearchBar },
} = unifiedSearch;
const CustomSearchBar = getCustomSearchBar(data);
const customUnifiedSearch = {
...unifiedSearch,
ui: {
...unifiedSearch.ui,
SearchBar: CustomSearchBar,
AggregateQuerySearchBar: CustomSearchBar,
},
};
return createTopNavWithCustomContext(customUnifiedSearch);
};

View file

@ -1,63 +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 { ClickTriggerEvent, MultiClickTriggerEvent } from '@kbn/charts-plugin/public';
import { ACTION_GLOBAL_APPLY_FILTER } from '@kbn/unified-search-plugin/public';
import type { DiscoverCustomization } from '@kbn/discover-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
export type WithPreventableEvent<T> = T & {
preventDefault(): void;
};
export type ClickTriggerEventData = ClickTriggerEvent['data'] | MultiClickTriggerEvent['data'];
type CustomClickTriggerEvent = WithPreventableEvent<ClickTriggerEventData>;
const isClickTriggerEvent = (
e: CustomClickTriggerEvent
): e is WithPreventableEvent<ClickTriggerEvent['data']> => {
return Array.isArray(e.data) && 'column' in e.data[0];
};
const isMultiValueTriggerEvent = (
e: CustomClickTriggerEvent
): e is WithPreventableEvent<MultiClickTriggerEvent['data']> => {
return Array.isArray(e.data) && 'cells' in e.data[0];
};
export const createCustomUnifiedHistogram = (
data: DataPublicPluginStart
): DiscoverCustomization => {
return {
id: 'unified_histogram',
onFilter: async (eventData) => {
eventData.preventDefault();
let filters;
if (isClickTriggerEvent(eventData)) {
filters = await data.actions.createFiltersFromValueClickAction(eventData);
} else if (isMultiValueTriggerEvent(eventData)) {
filters = await data.actions.createFiltersFromMultiValueClickAction(eventData);
}
if (filters && filters.length > 0) {
data.query.filterManager.addFilters(filters);
}
},
onBrushEnd: (eventData) => {
eventData.preventDefault();
const [from, to] = eventData.range;
data.query.timefilter.timefilter.setTime({
from: new Date(from).toISOString(),
to: new Date(to).toISOString(),
mode: 'absolute',
});
},
withDefaultActions: false,
disabledActions: [ACTION_GLOBAL_APPLY_FILTER],
};
};

View file

@ -1,163 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import type { CoreStart } from '@kbn/core/public';
import type { CustomizationCallback } from '@kbn/discover-plugin/public';
import { i18n } from '@kbn/i18n';
import { waitFor } from 'xstate/lib/waitFor';
import { dynamic } from '@kbn/shared-ux-utility';
import { UnifiedDocViewerLogsOverview } from '@kbn/unified-doc-viewer-plugin/public';
import type { LogsExplorerController } from '../controller';
import type { LogsExplorerStartDeps } from '../types';
import { useKibanaContextForPluginProvider } from '../utils/use_kibana';
import { createCustomSearchBar } from './custom_search_bar';
import { createCustomUnifiedHistogram } from './custom_unified_histogram';
const LazyCustomDataSourceFilters = dynamic(() => import('./custom_data_source_filters'));
const LazyCustomDataSourceSelector = dynamic(() => import('./custom_data_source_selector'));
export interface CreateLogsExplorerProfileCustomizationsDeps {
core: CoreStart;
plugins: LogsExplorerStartDeps;
controller: LogsExplorerController;
}
export const createLogsExplorerProfileCustomizations =
({
core,
plugins,
controller,
}: CreateLogsExplorerProfileCustomizationsDeps): CustomizationCallback =>
async ({ customizations, stateContainer }) => {
const { discoverServices, service } = controller;
const pluginsWithOverrides = {
...plugins,
...discoverServices,
};
const { data, dataViews, navigation, unifiedSearch } = pluginsWithOverrides;
service.send('RECEIVED_STATE_CONTAINER', { discoverStateContainer: stateContainer });
/**
* Wait for the machine to be fully initialized to set the restored selection
* create the DataView and set it in the stateContainer from Discover
*/
await waitFor(service, (state) => state.matches('initialized'), { timeout: 30000 });
/**
* Replace the DataViewPicker with a custom `DataSourceSelector` to pick integrations streams
* Prepend the search bar with custom filter control groups depending on the selected dataset
*/
customizations.set({
id: 'search_bar',
CustomDataViewPicker: () => {
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(core, plugins);
return (
<KibanaContextProviderForPlugin>
<LazyCustomDataSourceSelector
controller={controller}
core={core}
datasetsClient={controller.datasetsClient}
dataViews={dataViews}
logsExplorerControllerStateService={service}
/>
</KibanaContextProviderForPlugin>
);
},
PrependFilterBar: () => (
<LazyCustomDataSourceFilters logsExplorerControllerStateService={service} data={data} />
),
CustomSearchBar: createCustomSearchBar({
data,
navigation,
unifiedSearch,
}),
});
customizations.set({
id: 'data_table',
logsEnabled: true,
rowAdditionalLeadingControls: await import('./custom_control_column').then((module) =>
module.getRowAdditionalControlColumns()
),
});
customizations.set({
id: 'field_list',
/**
* We are switching from these virtual columns to the One Discover Summary column.
* In this effort we don't want to immediately cleanup everything about these virtual columns,
* but only disable their instantiation.
* We'll clean this part as soon as we decide to definitevely discard these columns.
**/
logsFieldsEnabled: false,
});
// Fix bug where filtering on histogram does not work
customizations.set(createCustomUnifiedHistogram(data));
/**
* Hide New, Open and Save settings to prevent working with saved views.
*/
customizations.set({
id: 'top_nav',
defaultMenu: {
newItem: { disabled: true },
openItem: { disabled: true },
saveItem: { disabled: true },
},
defaultBadges: {
unsavedChangesBadge: { disabled: true },
},
});
/**
* Flyout customization.
* The latest changes moved the implementation of the flyout overview tab into the unified_doc_viewer presets.
* To keep control over the overview tab and enable it only on the Logs Explorer,
* the docViewsRegistry is updated to allow enable/disable of any doc view.
* In a close future, when the contextual awareness for Discover will be in place,
* this configuration will be moved into a flavored logs experience directly defined in Discover.
*/
customizations.set({
id: 'flyout',
size: 650,
title: i18n.translate('xpack.logsExplorer.flyoutDetail.title', {
defaultMessage: 'Log details',
}),
actions: {
defaultActions: {
viewSingleDocument: { disabled: true },
viewSurroundingDocument: { disabled: true },
},
},
docViewsRegistry: (registry) => {
const logsAIAssistantFeature = plugins.discoverShared.features.registry.getById(
'observability-logs-ai-assistant'
);
registry.add({
id: 'doc_view_logs_overview',
title: i18n.translate('xpack.logsExplorer.docViews.logsOverview.title', {
defaultMessage: 'Overview',
}),
order: 0,
component: (props) => (
<UnifiedDocViewerLogsOverview
{...props}
renderAIAssistant={logsAIAssistantFeature?.render}
/>
),
});
return registry;
},
});
return () => {};
};

View file

@ -1,18 +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 { LogsExplorerControllerContext } from '../state_machines/logs_explorer_controller';
export type OnUknownDataViewSelectionHandler = (context: LogsExplorerControllerContext) => void;
export interface LogsExplorerCustomizationEvents {
onUknownDataViewSelection?: OnUknownDataViewSelectionHandler;
}
export interface LogsExplorerCustomizations {
events?: LogsExplorerCustomizationEvents;
}

View file

@ -1,52 +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 { ControlGroupRuntimeState, ControlGroupRendererApi } from '@kbn/controls-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { TimeRange } from '@kbn/es-query';
import { useQuerySubscriber } from '@kbn/unified-field-list';
import { useSelector } from '@xstate/react';
import { useCallback } from 'react';
import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller';
export const useControlPanels = (
logsExplorerControllerStateService: LogsExplorerControllerStateService,
data: DataPublicPluginStart
) => {
const { query, filters, fromDate, toDate } = useQuerySubscriber({ data });
const timeRange: TimeRange = { from: fromDate!, to: toDate! };
const controlPanels = useSelector(logsExplorerControllerStateService, (state) => {
if (!('controlPanels' in state.context)) return;
return state.context.controlPanels;
});
const getInitialState = useCallback(
async (initialState: Partial<ControlGroupRuntimeState>) => {
const state: Partial<ControlGroupRuntimeState> = {
...initialState,
initialChildControlState: controlPanels ?? initialState.initialChildControlState,
};
return { initialState: state };
},
[controlPanels]
);
const setControlGroupAPI = useCallback(
(controlGroupAPI: ControlGroupRendererApi | undefined) => {
if (controlGroupAPI)
logsExplorerControllerStateService.send({
type: 'INITIALIZE_CONTROL_GROUP_API',
controlGroupAPI,
});
},
[logsExplorerControllerStateService]
);
return { getInitialState, setControlGroupAPI, query, filters, timeRange };
};

View file

@ -1,31 +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 { useSelector } from '@xstate/react';
import { useCallback } from 'react';
import { DataSourceSelectionChangeHandler } from '../../common/data_source_selection';
import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller';
export const useDataSourceSelection = (
logsExplorerControllerStateService: LogsExplorerControllerStateService
) => {
const dataSourceSelection = useSelector(logsExplorerControllerStateService, (state) => {
return state.context.dataSourceSelection;
});
const allSelection = useSelector(logsExplorerControllerStateService, (state) => {
return state.context.allSelection;
});
const handleDataSourceSelectionChange: DataSourceSelectionChangeHandler = useCallback(
(data) => {
logsExplorerControllerStateService.send({ type: 'UPDATE_DATA_SOURCE_SELECTION', data });
},
[logsExplorerControllerStateService]
);
return { dataSourceSelection, allSelection, handleDataSourceSelectionChange };
};

View file

@ -1,135 +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 { useCallback } from 'react';
import createContainer from 'constate';
import { useInterpret, useSelector } from '@xstate/react';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { CoreStart } from '@kbn/core/public';
import { DEFAULT_ALLOWED_LOGS_BASE_PATTERNS } from '@kbn/discover-utils';
import { DataViewDescriptor } from '../../common/data_views/models/data_view_descriptor';
import { SortOrder } from '../../common/latest';
import { DataViewsFilterParams, createDataViewsStateMachine } from '../state_machines/data_views';
import { LogsExplorerCustomizations } from '../controller';
interface DataViewsContextDeps {
core: CoreStart;
dataViewsService: DataViewsPublicPluginStart;
events: LogsExplorerCustomizations['events'];
}
export interface SearchDataViewsParams {
name: string;
sortOrder: SortOrder;
}
export type SearchDataViews = (params: SearchDataViewsParams) => void;
export type FilterDataViews = (params: DataViewsFilterParams) => void;
export type LoadDataViews = () => void;
export type ReloadDataViews = () => void;
export type IsDataViewAllowed = (dataView: DataViewDescriptor) => boolean;
export type IsDataViewAvailable = (dataView: DataViewDescriptor) => boolean;
const useDataViews = ({ core, dataViewsService, events }: DataViewsContextDeps) => {
const dataViewsStateService = useInterpret(() =>
createDataViewsStateMachine({
dataViews: dataViewsService,
})
);
const dataViews = useSelector(dataViewsStateService, (state) => state.context.dataViews);
const dataViewCount = useSelector(
dataViewsStateService,
(state) => state.context.dataViewsSource?.length || 0
);
const error = useSelector(dataViewsStateService, (state) => state.context.error);
const isLoading = useSelector(dataViewsStateService, (state) => state.matches('loading'));
// Test whether a data view can be explored in Logs Explorer based on the settings
const isDataViewAllowed: IsDataViewAllowed = useCallback(
(dataView) => dataView.testAgainstAllowedList(DEFAULT_ALLOWED_LOGS_BASE_PATTERNS),
[]
);
// Test whether a data view can be explored in Logs Explorer based on the settings or has fallback handler
const isDataViewAvailable: IsDataViewAvailable = useCallback(
(dataView) => {
const isAllowedDataView = isDataViewAllowed(dataView);
return (
isAllowedDataView || (!isAllowedDataView && Boolean(events?.onUknownDataViewSelection))
);
},
[isDataViewAllowed, events?.onUknownDataViewSelection]
);
const loadDataViews = useCallback(
() => dataViewsStateService.send({ type: 'LOAD_DATA_VIEWS' }),
[dataViewsStateService]
);
const reloadDataViews = useCallback(
() => dataViewsStateService.send({ type: 'RELOAD_DATA_VIEWS' }),
[dataViewsStateService]
);
const searchDataViews: SearchDataViews = useCallback(
(searchParams) =>
dataViewsStateService.send({
type: 'SEARCH_DATA_VIEWS',
search: searchParams,
}),
[dataViewsStateService]
);
const filterDataViews: FilterDataViews = useCallback(
(filterParams) =>
dataViewsStateService.send({
type: 'FILTER_DATA_VIEWS',
filter: filterParams,
}),
[dataViewsStateService]
);
const sortDataViews: SearchDataViews = useCallback(
(searchParams) =>
dataViewsStateService.send({
type: 'SORT_DATA_VIEWS',
search: searchParams,
}),
[dataViewsStateService]
);
return {
// Underlying state machine
dataViewsStateService,
// Failure states
error,
// Loading states
isLoading,
// Data
dataViews,
dataViewCount,
// Actions
isDataViewAllowed,
isDataViewAvailable,
loadDataViews,
reloadDataViews,
searchDataViews,
filterDataViews,
sortDataViews,
};
};
export const [DataViewsProvider, useDataViewsContext] = createContainer(useDataViews);

View file

@ -1,104 +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 { useCallback } from 'react';
import createContainer from 'constate';
import { useInterpret, useSelector } from '@xstate/react';
import { FindDatasetsRequestQuery, SortOrder } from '../../common/latest';
import { IDatasetsClient } from '../services/datasets';
import { createDatasetsStateMachine } from '../state_machines/datasets';
interface DatasetsContextDeps {
datasetsClient: IDatasetsClient;
}
export interface SearchDatasetsParams {
name: string;
sortOrder: SortOrder;
}
export type SearchDatasets = (params: SearchDatasetsParams) => void;
export type LoadDatasets = () => void;
export type ReloadDatasets = () => void;
const useDatasets = ({ datasetsClient }: DatasetsContextDeps) => {
const datasetsStateService = useInterpret(() =>
createDatasetsStateMachine({
datasetsClient,
})
);
const datasets = useSelector(datasetsStateService, (state) => state.context.datasets);
const error = useSelector(datasetsStateService, (state) => state.context.error);
const isLoading = useSelector(
datasetsStateService,
(state) => state.matches('loading') || state.matches('debounceSearchingDatasets')
);
const loadDatasets = useCallback(
() => datasetsStateService.send({ type: 'LOAD_DATASETS' }),
[datasetsStateService]
);
const reloadDatasets = useCallback(
() => datasetsStateService.send({ type: 'RELOAD_DATASETS' }),
[datasetsStateService]
);
const searchDatasets: SearchDatasets = useCallback(
(searchParams) =>
datasetsStateService.send({
type: 'SEARCH_DATASETS',
search: formatSearchParams(searchParams),
}),
[datasetsStateService]
);
const sortDatasets: SearchDatasets = useCallback(
(searchParams) =>
datasetsStateService.send({
type: 'SORT_DATASETS',
search: formatSearchParams(searchParams),
}),
[datasetsStateService]
);
return {
// Underlying state machine
datasetsStateService,
// Failure states
error,
// Loading states
isLoading,
// Data
datasets,
// Actions
loadDatasets,
reloadDatasets,
searchDatasets,
sortDatasets,
};
};
export const [DatasetsProvider, useDatasetsContext] = createContainer(useDatasets);
/**
* Utils
*/
const formatSearchParams = ({
name,
...params
}: SearchDatasetsParams): FindDatasetsRequestQuery => ({
datasetQuery: name,
...params,
});

View file

@ -1,56 +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 { ENABLE_ESQL } from '@kbn/esql-utils';
import { DataSourceSelection, isDatasetSelection } from '../../common/data_source_selection';
import { useKibanaContextForPlugin } from '../utils/use_kibana';
export interface DiscoverEsqlUrlProps {
href?: string;
onClick: () => void;
}
export interface UseEsqlResult {
isEsqlEnabled: boolean;
discoverEsqlUrlProps: DiscoverEsqlUrlProps;
}
interface EsqlContextDeps {
dataSourceSelection: DataSourceSelection;
}
export const useEsql = ({ dataSourceSelection }: EsqlContextDeps): UseEsqlResult => {
const {
services: { uiSettings, discover },
} = useKibanaContextForPlugin();
const isEsqlEnabled = uiSettings?.get(ENABLE_ESQL);
const esqlPattern = isDatasetSelection(dataSourceSelection)
? dataSourceSelection.selection.dataset.name
: dataSourceSelection.selection.dataView.title;
const discoverLinkParams = {
query: {
esql: `FROM ${esqlPattern} | LIMIT 10`,
},
};
const href = discover.locator?.useUrl(discoverLinkParams);
const onClick = () => {
discover.locator?.navigate(discoverLinkParams);
};
return {
// Data
isEsqlEnabled,
discoverEsqlUrlProps: {
href,
onClick,
},
};
};

View file

@ -1,127 +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 { useCallback } from 'react';
import createContainer from 'constate';
import { useInterpret, useSelector } from '@xstate/react';
import { FindIntegrationsRequestQuery, SortOrder } from '../../common/latest';
import { IDatasetsClient } from '../services/datasets';
import { createIntegrationStateMachine } from '../state_machines/integrations';
interface IntegrationsContextDeps {
datasetsClient: IDatasetsClient;
}
export interface SearchIntegrationsParams {
name: string;
sortOrder: SortOrder;
integrationId?: string;
}
export type SearchIntegrations = (params: SearchIntegrationsParams) => void;
export type ReloadIntegrations = () => void;
export type LoadMoreIntegrations = () => void;
const useIntegrations = ({ datasetsClient }: IntegrationsContextDeps) => {
const integrationsStateService = useInterpret(() =>
createIntegrationStateMachine({
datasetsClient,
})
);
const integrations = useSelector(integrationsStateService, (state) => state.context.integrations);
const error = useSelector(integrationsStateService, (state) => state.context.error);
const isLoading = useSelector(integrationsStateService, (state) => state.matches('loading'));
const isSearching = useSelector(
integrationsStateService,
(state) =>
state.matches({ loaded: 'loadingMore' }) ||
state.matches({ loaded: 'debounceSearchingIntegrations' }) ||
state.matches({ loaded: 'debounceSearchingIntegrationsStreams' })
);
const searchIntegrations: SearchIntegrations = useCallback(
(searchParams) =>
integrationsStateService.send({
type: 'SEARCH_INTEGRATIONS',
search: formatSearchParams(searchParams),
}),
[integrationsStateService]
);
const sortIntegrations: SearchIntegrations = useCallback(
(searchParams) =>
integrationsStateService.send({
type: 'SORT_INTEGRATIONS',
search: formatSearchParams(searchParams),
}),
[integrationsStateService]
);
const searchIntegrationsStreams: SearchIntegrations = useCallback(
(searchParams) =>
integrationsStateService.send({
type: 'SEARCH_INTEGRATIONS_STREAMS',
search: formatSearchParams(searchParams),
}),
[integrationsStateService]
);
const sortIntegrationsStreams: SearchIntegrations = useCallback(
(searchParams) =>
integrationsStateService.send({
type: 'SORT_INTEGRATIONS_STREAMS',
search: formatSearchParams(searchParams),
}),
[integrationsStateService]
);
const reloadIntegrations = useCallback(
() => integrationsStateService.send({ type: 'RELOAD_INTEGRATIONS' }),
[integrationsStateService]
);
const loadMore = useCallback(
() => integrationsStateService.send({ type: 'LOAD_MORE_INTEGRATIONS' }),
[integrationsStateService]
);
return {
// Underlying state machine
integrationsStateService,
// Failure states
error,
// Loading states
isLoading,
isSearching,
// Data
integrations,
// Actions
loadMore,
reloadIntegrations,
searchIntegrations,
sortIntegrations,
searchIntegrationsStreams,
sortIntegrationsStreams,
};
};
export const [IntegrationsProvider, useIntegrationsContext] = createContainer(useIntegrations);
/**
* Utils
*/
const formatSearchParams = ({
name,
...params
}: SearchIntegrationsParams): FindIntegrationsRequestQuery => ({
nameQuery: name,
...params,
});

View file

@ -1,32 +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 { useEffect, useState } from 'react';
import useIntersection from 'react-use/lib/useIntersection';
interface IntersectionOptions {
onIntersecting?: () => void;
}
export function useIntersectionRef<ElementType extends HTMLElement = HTMLButtonElement>({
onIntersecting,
}: IntersectionOptions = {}) {
const [intersectionRef, setRef] = useState<ElementType | null>(null);
const intersection = useIntersection(
{ current: intersectionRef },
{ root: null, threshold: 0.5 }
);
useEffect(() => {
if (intersection?.isIntersecting && onIntersecting) {
onIntersecting();
}
}, [intersection, onIntersecting]);
return [setRef, intersection] as [typeof setRef, IntersectionObserverEntry | null];
}

View file

@ -1,34 +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 { PluginInitializerContext } from '@kbn/core/public';
import type { LogsExplorerConfig } from '../common/plugin_config';
import { LogsExplorerPlugin } from './plugin';
export type {
CreateLogsExplorerController,
LogsExplorerController,
LogsExplorerPublicState,
LogsExplorerPublicStateUpdate,
} from './controller';
export type {
LogsExplorerCustomizations,
LogsExplorerCustomizationEvents,
} from './customizations/types';
export type { LogsExplorerControllerContext } from './state_machines/logs_explorer_controller';
export { DEFAULT_ALL_SELECTION } from './state_machines/logs_explorer_controller/src/default_all_selection';
export type { LogsExplorerPluginSetup, LogsExplorerPluginStart } from './types';
export {
getDiscoverColumnsFromDisplayOptions,
getDiscoverGridFromDisplayOptions,
getDiscoverFiltersFromState,
getDiscoverColumnsWithFallbackFieldsFromDisplayOptions,
} from './utils/convert_discover_app_state';
export function plugin(context: PluginInitializerContext<LogsExplorerConfig>) {
return new LogsExplorerPlugin(context);
}

View file

@ -1,43 +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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
import { createLogsExplorer } from './components/logs_explorer';
import { createLogsExplorerControllerLazyFactory } from './controller/lazy_create_controller';
import type {
LogsExplorerPluginSetup,
LogsExplorerPluginStart,
LogsExplorerSetupDeps,
LogsExplorerStartDeps,
} from './types';
export class LogsExplorerPlugin
implements Plugin<LogsExplorerPluginSetup, LogsExplorerPluginStart>
{
constructor(context: PluginInitializerContext) {}
public setup(core: CoreSetup, plugins: LogsExplorerSetupDeps) {
return {};
}
public start(core: CoreStart, plugins: LogsExplorerStartDeps) {
const LogsExplorer = createLogsExplorer({
core,
plugins,
});
const createLogsExplorerController = createLogsExplorerControllerLazyFactory({
core,
plugins,
});
return {
LogsExplorer,
createLogsExplorerController,
};
}
}

View file

@ -1,13 +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 { IDatasetsClient } from './types';
export const createDatasetsClientMock = (): jest.Mocked<IDatasetsClient> => ({
findDatasets: jest.fn(),
findIntegrations: jest.fn(),
});

View file

@ -1,81 +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 { HttpStart } from '@kbn/core/public';
import { API_VERSIONS } from '@kbn/fleet-plugin/common';
import { decodeOrThrow } from '@kbn/io-ts-utils';
import { Dataset, Integration } from '../../../common/datasets';
import {
DATASETS_URL,
FindDatasetsRequestQuery,
findDatasetsRequestQueryRT,
findDatasetsResponseRT,
FindDatasetValue,
FindIntegrationsRequestQuery,
findIntegrationsRequestQueryRT,
findIntegrationsResponseRT,
FindIntegrationsValue,
INTEGRATIONS_URL,
} from '../../../common/latest';
import { FindDatasetsError, FindIntegrationsError } from '../../../common/datasets/errors';
import { IDatasetsClient } from './types';
const defaultIntegrationsParams: Pick<FindIntegrationsRequestQuery, 'dataStreamType'> = {
dataStreamType: 'logs',
};
const defaultDatasetsParams: Pick<FindDatasetsRequestQuery, 'type' | 'uncategorisedOnly'> = {
type: 'logs',
uncategorisedOnly: true,
};
export class DatasetsClient implements IDatasetsClient {
constructor(private readonly http: HttpStart) {}
public async findIntegrations(
params: FindIntegrationsRequestQuery = {}
): Promise<FindIntegrationsValue> {
const search = { ...defaultIntegrationsParams, ...params };
const query = findIntegrationsRequestQueryRT.encode(search);
const response = await this.http
.get(INTEGRATIONS_URL, { query, version: API_VERSIONS.public.v1 })
.catch((error) => {
throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`);
});
const data = decodeOrThrow(
findIntegrationsResponseRT,
(message: string) =>
new FindIntegrationsError(`Failed to decode integrations response: ${message}"`)
)(response);
return { ...data, items: data.items.map(Integration.create) };
}
public async findDatasets(params: FindDatasetsRequestQuery = {}): Promise<FindDatasetValue> {
const search = { ...defaultDatasetsParams, ...params };
const query = findDatasetsRequestQueryRT.encode(search);
const response = await this.http
.get(DATASETS_URL, { query, version: API_VERSIONS.public.v1 })
.catch((error) => {
throw new FindDatasetsError(`Failed to fetch data streams": ${error}`);
});
const data = decodeOrThrow(
findDatasetsResponseRT,
(message: string) =>
new FindDatasetsError(`Failed to decode data streams response: ${message}"`)
)(response);
return { items: data.items.map((dataset) => Dataset.create(dataset)) };
}
}

View file

@ -1,16 +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 { createDatasetsClientMock } from './datasets_client.mock';
import { DatasetsServiceStart } from './types';
export const createDatasetsServiceStartMock = () => ({
client: createDatasetsClientMock(),
});
export const _ensureTypeCompatibility = (): DatasetsServiceStart =>
createDatasetsServiceStartMock();

View file

@ -1,23 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DatasetsClient } from './datasets_client';
import { DatasetsServiceSetup, DatasetsServiceStart, DatasetsServiceStartDeps } from './types';
export class DatasetsService {
constructor() {}
public setup(): DatasetsServiceSetup {}
public start({ http }: DatasetsServiceStartDeps): DatasetsServiceStart {
const client = new DatasetsClient(http);
return {
client,
};
}
}

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './datasets_client';
export * from './datasets_service';
export * from './types';

View file

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { HttpStart } from '@kbn/core/public';
import {
FindDatasetsRequestQuery,
FindDatasetValue,
FindIntegrationsRequestQuery,
FindIntegrationsValue,
} from '../../../common/latest';
export type DatasetsServiceSetup = void;
export interface DatasetsServiceStart {
client: IDatasetsClient;
}
export interface DatasetsServiceStartDeps {
http: HttpStart;
}
export interface IDatasetsClient {
findDatasets(params?: FindDatasetsRequestQuery): Promise<FindDatasetValue>;
findIntegrations(params?: FindIntegrationsRequestQuery): Promise<FindIntegrationsValue>;
}

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