Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Index Patterns] Use deprecation api for scripted fields #100781

Merged
9 changes: 9 additions & 0 deletions src/plugins/data/server/index_patterns/deprecations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { createScriptedFieldsDeprecationsConfig } from './scripted_fields';
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { hasScriptedField } from './scripted_fields';

describe('hasScriptedField', () => {
test('valid index pattern object with a scripted field', () => {
expect(
hasScriptedField({
title: 'kibana_sample_data_logs*',
fields:
'[{"count":0,"script":"return 5;","lang":"painless","name":"test","type":"number","scripted":true,"searchable":true,"aggregatable":true,"readFromDocValues":false,"customLabel":""}]',
})
).toBe(true);
});

test('valid index pattern object without a scripted field', () => {
expect(
hasScriptedField({
title: 'kibana_sample_data_logs*',
fields: '[]',
})
).toBe(false);
});

test('invalid index pattern object', () => {
expect(
hasScriptedField({
title: 'kibana_sample_data_logs*',
fields: '[...]',
})
).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import {
CoreSetup,
DeprecationsDetails,
GetDeprecationsContext,
RegisterDeprecationsConfig,
} from 'kibana/server';
import { IndexPatternAttributes } from '../../../common';

type IndexPatternAttributesWithFields = Pick<IndexPatternAttributes, 'title' | 'fields'>;

export const createScriptedFieldsDeprecationsConfig: (
core: CoreSetup
) => RegisterDeprecationsConfig = (core: CoreSetup) => ({
getDeprecations: async (context: GetDeprecationsContext): Promise<DeprecationsDetails[]> => {
const finder = context.savedObjectsClient.createPointInTimeFinder<IndexPatternAttributesWithFields>(
{
type: 'index-pattern',
perPage: 1000,
fields: ['title', 'fields'],
}
);

const indexPatternsWithScriptedFields: IndexPatternAttributesWithFields[] = [];
for await (const response of finder.find()) {
indexPatternsWithScriptedFields.push(
...response.saved_objects.map((so) => so.attributes).filter(hasScriptedField)
);
}

if (indexPatternsWithScriptedFields.length > 0) {
const PREVIEW_LIMIT = 3;
const indexPatternTitles = indexPatternsWithScriptedFields.map((ip) => ip.title);
const titlesPreview = indexPatternTitles.slice(0, PREVIEW_LIMIT).join('; ');
const allTitles = indexPatternTitles.join('; ');

return [
{
message: `You have ${indexPatternsWithScriptedFields.length} index patterns (${titlesPreview}...) that use scripted fields. Scripted fields are deprecated and will be removed in future. Use runtime fields instead.`,
documentationUrl:
'https://www.elastic.co/guide/en/elasticsearch/reference/7.x/runtime.html', // TODO: documentation service is not available serverside https://github.com/elastic/kibana/issues/95389
level: 'warning', // warning because it is not set in stone WHEN we remove scripted fields, hence this deprecation is not a blocker for 8.0 upgrade
correctiveActions: {
manualSteps: [
'Navigate to Stack Management > Kibana > Index Patterns.',
`Update ${indexPatternsWithScriptedFields.length} index patterns that have scripted fields to use runtime fields instead. In most cases, to migrate existing scripts, you'll need to change "return <value>;" to "emit(<value>);". Index patterns with at least one scripted field: ${allTitles}`,
],
},
},
];
} else {
return [];
}
},
});

export function hasScriptedField(indexPattern: IndexPatternAttributesWithFields) {
if (indexPattern.fields) {
try {
return JSON.parse(indexPattern.fields).some(
(field: { scripted?: boolean }) => field?.scripted
);
} catch (e) {
return false;
}
} else {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { UiSettingsServerToCommon } from './ui_settings_wrapper';
import { IndexPatternsApiServer } from './index_patterns_api_client';
import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper';
import { registerIndexPatternsUsageCollector } from './register_index_pattern_usage_collection';
import { createScriptedFieldsDeprecationsConfig } from './deprecations';

export interface IndexPatternsServiceStart {
indexPatternsServiceFactory: (
Expand Down Expand Up @@ -88,6 +89,7 @@ export class IndexPatternsServiceProvider implements Plugin<void, IndexPatternsS

expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices }));
registerIndexPatternsUsageCollector(core.getStartServices, usageCollection);
core.deprecations.registerDeprecations(createScriptedFieldsDeprecationsConfig(core));
}

public start(core: CoreStart, { fieldFormats, logger }: IndexPatternsServiceStartDeps) {
Expand Down
15 changes: 15 additions & 0 deletions test/api_integration/apis/index_patterns/deprecations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ loadTestFile }: FtrProviderContext) {
describe('scripted_fields_deprecations', () => {
loadTestFile(require.resolve('./scripted_fields'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import expect from '@kbn/expect';
import type { DeprecationsGetResponse } from 'src/core/server/types';
import { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');

describe('scripted field deprecations', () => {
before(async () => {
await esArchiver.emptyKibanaIndex();
await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index');
});

after(async () => {
await esArchiver.unload(
'test/api_integration/fixtures/es_archiver/index_patterns/basic_index'
);
});

it('no scripted fields deprecations', async () => {
const { body } = await supertest.get('/api/deprecations/');

const { deprecations } = body as DeprecationsGetResponse;
const dataPluginDeprecations = deprecations.filter(({ domainId }) => domainId === 'data');

expect(dataPluginDeprecations.length).to.be(0);
});

it('scripted field deprecation', async () => {
const title = `basic_index`;
await supertest.post('/api/index_patterns/index_pattern').send({
index_pattern: {
title,
fields: {
foo: {
name: 'foo',
type: 'string',
scripted: true,
script: "doc['field_name'].value",
},
bar: {
name: 'bar',
type: 'number',
scripted: true,
script: "doc['field_name'].value",
},
},
},
});

const { body } = await supertest.get('/api/deprecations/');
const { deprecations } = body as DeprecationsGetResponse;
const dataPluginDeprecations = deprecations.filter(({ domainId }) => domainId === 'data');

expect(dataPluginDeprecations.length).to.be(1);
expect(dataPluginDeprecations[0].message).to.contain(title);
});
});
}
1 change: 1 addition & 0 deletions test/api_integration/apis/index_patterns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./default_index_pattern'));
loadTestFile(require.resolve('./runtime_fields_crud'));
loadTestFile(require.resolve('./integration'));
loadTestFile(require.resolve('./deprecations'));
});
}