From bc159fdea9ff8fed2eeccd02fbe2cdb5e9460f45 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 5 May 2023 10:38:49 +0100 Subject: [PATCH 1/5] [ML] Fixing saved object sync in a serverless environment when apis are missing (#156585) Temporarily fixes https://github.com/elastic/kibana/issues/156500 In a serverless environment various elasticsearch apis may be missing depending on the project. We should allow these errors and ensure that saved objects related to the missing api are not synced. In the near future we should hopefully be able to determine which apis are missing before attempting to call them. --- .../plugins/ml/server/saved_objects/checks.ts | 38 +++---- .../plugins/ml/server/saved_objects/util.ts | 105 +++++++++++++++++- 2 files changed, 115 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/ml/server/saved_objects/checks.ts b/x-pack/plugins/ml/server/saved_objects/checks.ts index 544c927b5f6c51..1985115d6ebf15 100644 --- a/x-pack/plugins/ml/server/saved_objects/checks.ts +++ b/x-pack/plugins/ml/server/saved_objects/checks.ts @@ -20,9 +20,8 @@ import type { MlSavedObjectType, } from '../../common/types/saved_objects'; -import type { DataFrameAnalyticsConfig } from '../../common/types/data_frame_analytics'; import type { ResolveMlCapabilities } from '../../common/types/capabilities'; -import { getJobDetailsFromTrainedModel } from './util'; +import { getJobDetailsFromTrainedModel, getJobsAndModels } from './util'; export interface JobSavedObjectStatus { jobId: string; @@ -79,7 +78,7 @@ export function checksFactory( mlSavedObjectService: MLSavedObjectService ) { async function checkStatus(): Promise { - const [ + const { jobObjects, allJobObjects, modelObjects, @@ -88,18 +87,7 @@ export function checksFactory( datafeeds, dfaJobs, models, - ] = await Promise.all([ - mlSavedObjectService.getAllJobObjects(undefined, false), - mlSavedObjectService.getAllJobObjectsForAllSpaces(), - mlSavedObjectService.getAllTrainedModelObjects(false), - mlSavedObjectService.getAllTrainedModelObjectsForAllSpaces(), - client.asInternalUser.ml.getJobs(), - client.asInternalUser.ml.getDatafeeds(), - client.asInternalUser.ml.getDataFrameAnalytics() as unknown as { - data_frame_analytics: DataFrameAnalyticsConfig[]; - }, - client.asInternalUser.ml.getTrainedModels(), - ]); + } = await getJobsAndModels(client, mlSavedObjectService); const jobSavedObjectsStatus: JobSavedObjectStatus[] = jobObjects.map( ({ attributes, namespaces }) => { @@ -111,10 +99,10 @@ export function checksFactory( let datafeedExists: boolean | undefined; if (type === 'anomaly-detector') { - jobExists = adJobs.jobs.some((j) => j.job_id === jobId); - datafeedExists = datafeeds.datafeeds.some((d) => d.job_id === jobId); + jobExists = adJobs.some((j) => j.job_id === jobId); + datafeedExists = datafeeds.some((d) => d.job_id === jobId); } else { - jobExists = dfaJobs.data_frame_analytics.some((j) => j.id === jobId); + jobExists = dfaJobs.some((j) => j.id === jobId); } return { @@ -130,12 +118,12 @@ export function checksFactory( } ); - const dfaJobsCreateTimeMap = dfaJobs.data_frame_analytics.reduce((acc, cur) => { + const dfaJobsCreateTimeMap = dfaJobs.reduce((acc, cur) => { acc.set(cur.id, cur.create_time!); return acc; }, new Map()); - const modelJobExits = models.trained_model_configs.reduce((acc, cur) => { + const modelJobExits = models.reduce((acc, cur) => { const job = getJobDetailsFromTrainedModel(cur); if (job === null) { return acc; @@ -152,7 +140,7 @@ export function checksFactory( const modelSavedObjectsStatus: TrainedModelSavedObjectStatus[] = modelObjects.map( ({ attributes: { job, model_id: modelId }, namespaces }) => { - const trainedModelExists = models.trained_model_configs.some((m) => m.model_id === modelId); + const trainedModelExists = models.some((m) => m.model_id === modelId); const dfaJobExists = modelJobExits.get(modelId) ?? null; return { @@ -194,14 +182,14 @@ export function checksFactory( ); const modelObjectIds = new Set(modelSavedObjectsStatus.map(({ modelId }) => modelId)); - const anomalyDetectorsStatus = adJobs.jobs + const anomalyDetectorsStatus = adJobs .filter(({ job_id: jobId }) => { // only list jobs which are in the current space (adObjectIds) // or are not in any spaces (nonSpaceADObjectIds) return adObjectIds.has(jobId) === true || nonSpaceADObjectIds.has(jobId) === false; }) .map(({ job_id: jobId }) => { - const datafeedId = datafeeds.datafeeds.find((df) => df.job_id === jobId)?.datafeed_id; + const datafeedId = datafeeds.find((df) => df.job_id === jobId)?.datafeed_id; return { jobId, datafeedId: datafeedId ?? null, @@ -211,7 +199,7 @@ export function checksFactory( }; }); - const dataFrameAnalyticsStatus = dfaJobs.data_frame_analytics + const dataFrameAnalyticsStatus = dfaJobs .filter(({ id: jobId }) => { // only list jobs which are in the current space (dfaObjectIds) // or are not in any spaces (nonSpaceDFAObjectIds) @@ -227,7 +215,7 @@ export function checksFactory( }; }); - const modelsStatus = models.trained_model_configs + const modelsStatus = models .filter(({ model_id: modelId }) => { // only list jobs which are in the current space (adObjectIds) // or are not in any spaces (nonSpaceADObjectIds) diff --git a/x-pack/plugins/ml/server/saved_objects/util.ts b/x-pack/plugins/ml/server/saved_objects/util.ts index 561c09af75c985..bbd4e17d7a47dd 100644 --- a/x-pack/plugins/ml/server/saved_objects/util.ts +++ b/x-pack/plugins/ml/server/saved_objects/util.ts @@ -6,10 +6,14 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { SavedObjectsServiceStart, KibanaRequest } from '@kbn/core/server'; -import { SavedObjectsClient } from '@kbn/core/server'; +import { + type SavedObjectsServiceStart, + type KibanaRequest, + type IScopedClusterClient, + SavedObjectsClient, +} from '@kbn/core/server'; +import type { TrainedModelJob, MLSavedObjectService } from './service'; import { ML_JOB_SAVED_OBJECT_TYPE } from '../../common/types/saved_objects'; -import type { TrainedModelJob } from './service'; export function savedObjectClientsFactory( getSavedObjectsStart: () => SavedObjectsServiceStart | null @@ -57,3 +61,98 @@ export function getJobDetailsFromTrainedModel( const createTime: number = model.metadata.analytics_config.create_time; return { job_id: jobId, create_time: createTime }; } + +/* + * Function for calling elasticsearch APIs for retrieving ML jobs and models. + * The elasticsearch api may be missing in a serverless environment, in which case + * we return null. + */ + +function mlFunctionsFactory(client: IScopedClusterClient) { + return { + async getJobs() { + try { + return client.asInternalUser.ml.getJobs(); + } catch (error) { + return null; + } + }, + async getDatafeeds() { + try { + return client.asInternalUser.ml.getDatafeeds(); + } catch (error) { + return null; + } + }, + async getTrainedModels() { + try { + return client.asInternalUser.ml.getTrainedModels(); + } catch (error) { + return null; + } + }, + async getDataFrameAnalytics() { + try { + return client.asInternalUser.ml.getDataFrameAnalytics(); + } catch (error) { + return null; + } + }, + }; +} + +/* + * Function for retrieving lists of jobs, models and saved objects. + * If any of the elasticsearch APIs are missing, it returns empty arrays + * so that the sync process does not create or delete any saved objects. + */ + +export async function getJobsAndModels( + client: IScopedClusterClient, + mlSavedObjectService: MLSavedObjectService +) { + const { getJobs, getDatafeeds, getTrainedModels, getDataFrameAnalytics } = + mlFunctionsFactory(client); + + const [ + jobObjects, + allJobObjects, + modelObjects, + allModelObjects, + adJobs, + datafeeds, + dfaJobs, + models, + ] = await Promise.all([ + mlSavedObjectService.getAllJobObjects(undefined, false), + mlSavedObjectService.getAllJobObjectsForAllSpaces(), + mlSavedObjectService.getAllTrainedModelObjects(false), + mlSavedObjectService.getAllTrainedModelObjectsForAllSpaces(), + getJobs(), + getDatafeeds(), + getDataFrameAnalytics(), + getTrainedModels(), + ]); + + const adJobObjects = + adJobs !== null ? jobObjects.filter((j) => j.attributes.type === 'anomaly-detector') : []; + const adAllJobObjects = + adJobs !== null ? allJobObjects.filter((j) => j.attributes.type === 'anomaly-detector') : []; + const dfaJobObjects = + dfaJobs !== null ? jobObjects.filter((j) => j.attributes.type === 'data-frame-analytics') : []; + const dfaAllJobObjects = + dfaJobs !== null + ? allJobObjects.filter((j) => j.attributes.type === 'data-frame-analytics') + : []; + + return { + jobObjects: [...adJobObjects, ...dfaJobObjects], + allJobObjects: [...adAllJobObjects, ...dfaAllJobObjects], + modelObjects: models === null ? [] : modelObjects, + allModelObjects: models === null ? [] : allModelObjects, + adJobs: adJobs === null ? [] : adJobs.jobs, + datafeeds: datafeeds === null ? [] : datafeeds.datafeeds, + dfaJobs: dfaJobs === null ? [] : dfaJobs.data_frame_analytics, + models: models === null ? [] : models.trained_model_configs, + }; +} From ac18e8a3b0880d5667228da96a89558553a25bea Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 5 May 2023 12:42:11 +0300 Subject: [PATCH 2/5] [Visualizations][Lens] Hide incompatible actions from the panels (#156667) ## Summary Closes https://github.com/elastic/kibana/issues/156119 Closes https://github.com/elastic/kibana/issues/156114 This PR 1. Removes the convert to Lens action if visualize permissions are set to None image 2. Removes the edit actions if visualize permisions are set to None image ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../public/actions/edit_in_lens_action.tsx | 11 ++++++-- .../create_vis_embeddable_from_object.ts | 1 + .../embeddable/visualize_embeddable.tsx | 6 +++-- .../public/embeddable/embeddable.test.tsx | 27 +++++++++++++++++++ .../lens/public/embeddable/embeddable.tsx | 5 +++- .../public/embeddable/embeddable_factory.ts | 1 + 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index 07268726359bad..b86e2b2dbb37a5 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -17,7 +17,13 @@ import { IEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { Action } from '@kbn/ui-actions-plugin/public'; import { VisualizeEmbeddable } from '../embeddable'; import { DASHBOARD_VISUALIZATION_PANEL_TRIGGER } from '../triggers'; -import { getUiActions, getApplication, getEmbeddable, getUsageCollection } from '../services'; +import { + getUiActions, + getApplication, + getEmbeddable, + getUsageCollection, + getCapabilities, +} from '../services'; export const ACTION_EDIT_IN_LENS = 'ACTION_EDIT_IN_LENS'; @@ -116,7 +122,8 @@ export class EditInLensAction implements Action { async isCompatible(context: ActionExecutionContext) { const { embeddable } = context; - if (!isVisualizeEmbeddable(embeddable)) { + const { visualize } = getCapabilities(); + if (!isVisualizeEmbeddable(embeddable) || !visualize.show) { return false; } const vis = embeddable.getVis(); diff --git a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts index 8fb107f827cd6a..fdcdc25d21043f 100644 --- a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts +++ b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts @@ -62,6 +62,7 @@ export const createVisEmbeddableFromObject = const capabilities = { visualizeSave: Boolean(getCapabilities().visualize.save), dashboardSave: Boolean(getCapabilities().dashboard?.showWriteControls), + visualizeOpen: Boolean(getCapabilities().visualize?.show), }; return createVisualizeEmbeddableAsync( diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index a4cf5466422b29..a8eaca1552cd07 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -62,7 +62,7 @@ export interface VisualizeEmbeddableConfiguration { indexPatterns?: DataView[]; editPath: string; editUrl: string; - capabilities: { visualizeSave: boolean; dashboardSave: boolean }; + capabilities: { visualizeSave: boolean; dashboardSave: boolean; visualizeOpen: boolean }; deps: VisualizeEmbeddableFactoryDeps; } @@ -171,7 +171,9 @@ export class VisualizeEmbeddable if (this.attributeService) { const isByValue = !this.inputIsRefType(initialInput); - const editable = capabilities.visualizeSave || (isByValue && capabilities.dashboardSave); + const editable = + capabilities.visualizeSave || + (isByValue && capabilities.dashboardSave && capabilities.visualizeOpen); this.updateOutput({ ...this.getOutput(), editable }); } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index 5e48e1f46dd638..1ab012ef844835 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -166,6 +166,7 @@ describe('embeddable', () => { capabilities: { canSaveDashboards: true, canSaveVisualizations: true, + canOpenVisualizations: true, discover: {}, navLinks: {}, }, @@ -361,6 +362,7 @@ describe('embeddable', () => { capabilities: { canSaveDashboards: true, canSaveVisualizations: true, + canOpenVisualizations: true, discover: {}, navLinks: {}, }, @@ -413,6 +415,7 @@ describe('embeddable', () => { capabilities: { canSaveDashboards: true, canSaveVisualizations: true, + canOpenVisualizations: true, discover: {}, navLinks: {}, }, @@ -940,6 +943,7 @@ describe('embeddable', () => { capabilities: { canSaveDashboards: true, canSaveVisualizations: true, + canOpenVisualizations: true, discover: {}, navLinks: {}, }, @@ -1039,6 +1043,7 @@ describe('embeddable', () => { capabilities: { canSaveDashboards: true, canSaveVisualizations: true, + canOpenVisualizations: true, discover: {}, navLinks: {}, }, @@ -1135,6 +1140,7 @@ describe('embeddable', () => { capabilities: { canSaveDashboards: true, canSaveVisualizations: true, + canOpenVisualizations: true, discover: {}, navLinks: {}, }, @@ -1186,4 +1192,25 @@ describe('embeddable', () => { }) ); }); + + it('should not be editable for no visualize library privileges', async () => { + const embeddable = new Embeddable( + getEmbeddableProps({ + capabilities: { + canSaveDashboards: false, + canSaveVisualizations: true, + canOpenVisualizations: false, + discover: {}, + navLinks: {}, + }, + }), + { + timeRange: { + from: 'now-15m', + to: 'now', + }, + } as LensEmbeddableInput + ); + expect(embeddable.getOutput().editable).toBeUndefined(); + }); }); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 787200e53315b0..43a871ccf326f6 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -208,6 +208,7 @@ export interface LensEmbeddableDeps { getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions']; capabilities: { canSaveVisualizations: boolean; + canOpenVisualizations: boolean; canSaveDashboards: boolean; navLinks: Capabilities['navLinks']; discover: Capabilities['discover']; @@ -1353,7 +1354,9 @@ export class Embeddable private getIsEditable() { return ( this.deps.capabilities.canSaveVisualizations || - (!this.inputIsRefType(this.getInput()) && this.deps.capabilities.canSaveDashboards) + (!this.inputIsRefType(this.getInput()) && + this.deps.capabilities.canSaveDashboards && + this.deps.capabilities.canOpenVisualizations) ); } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts index f57e532bc13804..f31e8dd7dcad71 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts @@ -140,6 +140,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { capabilities: { canSaveDashboards: Boolean(capabilities.dashboard?.showWriteControls), canSaveVisualizations: Boolean(capabilities.visualize.save), + canOpenVisualizations: Boolean(capabilities.visualize.show), navLinks: capabilities.navLinks, discover: capabilities.discover, }, From 9b6cbfa0bba77c8e6e6add3a8da878cf581d6d69 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 11:49:05 +0200 Subject: [PATCH 3/5] Update XState (main) (#156602) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 ++-- yarn.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 024d984cd58959..391b8d31127ed1 100644 --- a/package.json +++ b/package.json @@ -742,7 +742,7 @@ "@turf/distance": "6.0.1", "@turf/helpers": "6.0.1", "@turf/length": "^6.0.2", - "@xstate/react": "^3.2.1", + "@xstate/react": "^3.2.2", "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "adm-zip": "^0.5.9", @@ -965,7 +965,7 @@ "vinyl": "^2.2.0", "whatwg-fetch": "^3.0.0", "xml2js": "^0.4.22", - "xstate": "^4.37.1", + "xstate": "^4.37.2", "xterm": "^5.1.0", "yauzl": "^2.10.0", "yazl": "^2.5.1" diff --git a/yarn.lock b/yarn.lock index 8819b232d4ac20..287ebf23462f97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9903,7 +9903,7 @@ resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== -"@xstate/react@^3.2.1": +"@xstate/react@^3.2.2": version "3.2.2" resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.2.2.tgz#ddf0f9d75e2c19375b1e1b7335e72cb99762aed8" integrity sha512-feghXWLedyq8JeL13yda3XnHPZKwYDN5HPBLykpLeuNpr9178tQd2/3d0NrH6gSd0sG5mLuLeuD+ck830fgzLQ== @@ -29656,7 +29656,7 @@ xpath@0.0.32: resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== -xstate@^4.37.1: +xstate@^4.37.2: version "4.37.2" resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.37.2.tgz#c5f4c1d8062784238b91e2dfddca05f821cb4eac" integrity sha512-Qm337O49CRTZ3PRyRuK6b+kvI+D3JGxXIZCTul+xEsyFCVkTFDt5jixaL1nBWcUBcaTQ9um/5CRGVItPi7fveg== From 0e259c859884e441b5aaccdd2223df49ac444855 Mon Sep 17 00:00:00 2001 From: Yan Savitski Date: Fri, 5 May 2023 11:52:21 +0200 Subject: [PATCH 4/5] [Enterprise Search][Behavioral Analytics] Update trackSearch and trackSearchClick params (#156735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the filters, results.items, page example for: - ✔️ trackSearch - ✔️ trackSearchClick image image --- ...tion_integrate_javascript_client_embed.tsx | 19 +++++++++++++++---- ..._collection_integrate_javascript_embed.tsx | 8 ++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_client_embed.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_client_embed.tsx index c240d941c74b5b..f5e07851ee4fea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_client_embed.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_client_embed.tsx @@ -173,15 +173,26 @@ const SearchResult = ({ hit }) => { const clickHandler = () => { trackSearchClick({ document: { id: hit.id, index: "products" }, + page: { + url: "http://my-website.com/products/123" + }, search: { query: "search term", - filters: [], + filters: {}, page: { current: 1, size: 10 }, - results: { + results: { items: [ - { id: "123", index: "products" } + { + document: { + id: "123", + index: "products", + }, + page: { + url: "http://my-website.com/products/123", + }, + }, ], - total_results: 10 + total_results: 10 }, sort: { name: "relevance", diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx index b44b19042d60cf..87295fd8eff6b0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx @@ -115,10 +115,10 @@ window.elasticAnalytics.createTracker({ {`window.elasticAnalytics.trackSearch({ search: { query: "laptop", - filters: [ - { field: "brand", value: ["apple"] }, - { field: "price", value: ["1000-2000"] }, - ], + filters: { + brand: ["apple"], + price: ["1000-2000"], + }, page: { current: 1, size: 10, From 14f5b9e883a4dd9e8ec85ad62b316caba7bf42ac Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 5 May 2023 10:56:30 +0100 Subject: [PATCH 5/5] [ML] Fixing expired license redirection (#156703) Fixes https://github.com/elastic/kibana/issues/150539 When handling route resolver errors, we now check to see if the error was triggered by an expired license, if so we redirect to the license management page rather than the access denied page. This is hard to test as you need an expired license. One way to spoof this is by editing code in the licensing plugin to inject expired license details. ``` response.license.expiry_date_in_millis = 1683205426704; response.license.status = 'expired'; ``` Adding this below [this line](https://github.com/elastic/kibana/blob/411ff0d0aefe795afe4b642dcf19e68ca42d75f2/x-pack/plugins/licensing/server/plugin.ts#L184). --- x-pack/plugins/ml/public/application/app.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index b15abc1d2cddbc..98f971f72e2e72 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -20,6 +20,7 @@ import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { StorageContextProvider } from '@kbn/ml-local-storage'; +import { firstValueFrom } from 'rxjs'; import { mlCapabilities } from './capabilities/check_capabilities'; import { ML_STORAGE_KEYS } from '../../common/types/storage'; import { ML_APP_LOCATOR, ML_PAGES } from '../../common/constants/locator'; @@ -74,10 +75,19 @@ export type MlGlobalServices = ReturnType; const App: FC = ({ coreStart, deps, appMountParams }) => { const redirectToMlAccessDeniedPage = async () => { - const accessDeniedPageUrl = await deps.share.url.locators.get(ML_APP_LOCATOR)!.getUrl({ - page: ML_PAGES.ACCESS_DENIED, - }); - await coreStart.application.navigateToUrl(accessDeniedPageUrl); + // access maybe be denied due to an expired license, so check the license status first + // if the license has expired, redirect to the license management page + const license = await firstValueFrom(deps.licensing.license$); + const redirectPage = + license.status === 'expired' + ? deps.share.url.locators.get('LICENSE_MANAGEMENT_LOCATOR')!.getUrl({ + page: 'dashboard', + }) + : deps.share.url.locators.get(ML_APP_LOCATOR)!.getUrl({ + page: ML_PAGES.ACCESS_DENIED, + }); + + await coreStart.application.navigateToUrl(await redirectPage); }; const pageDeps = {