+
{isCustomLinksPopoverOpen ? (
{
state: expect.objectContaining({
layers: expect.objectContaining({
currentLayer: expect.objectContaining({
- columnOrder: ['cola', 'id1'],
+ columnOrder: ['cola', 'colb'],
columns: {
cola: initialState.layers.currentLayer.columns.cola,
- id1: expect.objectContaining({
+ colb: expect.objectContaining({
operationType: 'avg',
sourceField: 'memory',
}),
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts
index 96127caa67bb42..d339171a5ae1f6 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts
@@ -15,8 +15,8 @@ import {
operationDefinitionMap,
IndexPatternColumn,
} from './operations';
-import { hasField } from './utils';
import { operationDefinitions } from './operations/definitions';
+import { hasField } from './utils';
import {
IndexPattern,
IndexPatternPrivateState,
@@ -196,7 +196,7 @@ function addFieldAsMetricOperation(
suggestedPriority: undefined,
field,
});
- const newColumnId = generateId();
+ const addedColumnId = generateId();
const [, metrics] = separateBucketColumns(layer);
@@ -206,20 +206,19 @@ function addFieldAsMetricOperation(
indexPatternId: indexPattern.id,
columns: {
...layer.columns,
- [newColumnId]: newColumn,
+ [addedColumnId]: newColumn,
},
- columnOrder: [...layer.columnOrder, newColumnId],
+ columnOrder: [...layer.columnOrder, addedColumnId],
};
}
- // If only one metric, replace instead of add
- const newColumns = { ...layer.columns, [newColumnId]: newColumn };
- delete newColumns[metrics[0]];
+ // Replacing old column with new column, keeping the old ID
+ const newColumns = { ...layer.columns, [metrics[0]]: newColumn };
return {
indexPatternId: indexPattern.id,
columns: newColumns,
- columnOrder: [...layer.columnOrder.filter(c => c !== metrics[0]), newColumnId],
+ columnOrder: layer.columnOrder, // Order is kept by replacing
};
}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx
index 226246714f18df..fc0c9746b2f989 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx
@@ -274,7 +274,7 @@ describe('terms', () => {
expect(updatedColumn).toBe(initialColumn);
});
- it('should switch to alphabetical ordering if the order column is removed', () => {
+ it('should switch to alphabetical ordering if there are no columns to order by', () => {
const termsColumn = termsOperation.onOtherColumnChanged!(
{
label: 'Top value of category',
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx
index cd0dcc0b7e9ce2..387b197c9235cc 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx
@@ -135,7 +135,7 @@ export const termsOperation: OperationDefinition = {
}
return currentColumn;
},
- paramEditor: ({ state, setState, currentColumn, columnId: currentColumnId, layerId }) => {
+ paramEditor: ({ state, setState, currentColumn, layerId }) => {
const SEPARATOR = '$$$';
function toValue(orderBy: TermsIndexPatternColumn['params']['orderBy']) {
if (orderBy.type === 'alphabetical') {
diff --git a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap b/x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap
similarity index 100%
rename from x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap
rename to x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap
diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts
similarity index 88%
rename from x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js
rename to x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts
index 4870e1e35cdaf8..f0afade8629ab4 100644
--- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js
+++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+// @ts-ignore
import Puid from 'puid';
import sinon from 'sinon';
import nodeCrypto from '@elastic/node-crypto';
@@ -13,36 +14,40 @@ import { createMockReportingCore } from '../../../test_helpers';
import { LevelLogger } from '../../../server/lib/level_logger';
import { setFieldFormats } from '../../../server/services';
import { executeJobFactory } from './execute_job';
+import { JobDocPayloadDiscoverCsv } from '../types';
import { CSV_BOM_CHARS } from '../../../common/constants';
-const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms));
+const delay = (ms: number) => new Promise(resolve => setTimeout(() => resolve(), ms));
const puid = new Puid();
const getRandomScrollId = () => {
return puid.generate();
};
+const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadDiscoverCsv;
+
describe('CSV Execute Job', function() {
const encryptionKey = 'testEncryptionKey';
const headers = {
sid: 'test',
};
const mockLogger = new LevelLogger({
- get: () => ({
- debug: jest.fn(),
- warn: jest.fn(),
- error: jest.fn(),
- }),
+ get: () =>
+ ({
+ debug: jest.fn(),
+ warn: jest.fn(),
+ error: jest.fn(),
+ } as any),
});
- let defaultElasticsearchResponse;
- let encryptedHeaders;
+ let defaultElasticsearchResponse: any;
+ let encryptedHeaders: any;
- let clusterStub;
- let configGetStub;
- let mockReportingConfig;
- let mockReportingPlugin;
- let callAsCurrentUserStub;
- let cancellationToken;
+ let clusterStub: any;
+ let configGetStub: any;
+ let mockReportingConfig: any;
+ let mockReportingPlugin: any;
+ let callAsCurrentUserStub: any;
+ let cancellationToken: any;
const mockElasticsearch = {
dataClient: {
@@ -78,7 +83,7 @@ describe('CSV Execute Job', function() {
_scroll_id: 'defaultScrollId',
};
clusterStub = {
- callAsCurrentUser: function() {},
+ callAsCurrentUser() {},
};
callAsCurrentUserStub = sinon
@@ -89,17 +94,19 @@ describe('CSV Execute Job', function() {
mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true);
setFieldFormats({
- fieldFormatServiceFactory: function() {
+ fieldFormatServiceFactory() {
const uiConfigMock = {};
- uiConfigMock['format:defaultTypeMap'] = {
+ (uiConfigMock as any)['format:defaultTypeMap'] = {
_default_: { id: 'string', params: {} },
};
const fieldFormatsRegistry = new fieldFormats.FieldFormatsRegistry();
- fieldFormatsRegistry.init(key => uiConfigMock[key], {}, [fieldFormats.StringFormat]);
+ fieldFormatsRegistry.init(key => (uiConfigMock as any)[key], {}, [
+ fieldFormats.StringFormat,
+ ]);
- return fieldFormatsRegistry;
+ return Promise.resolve(fieldFormatsRegistry);
},
});
});
@@ -109,7 +116,11 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
await executeJob(
'job456',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
expect(callAsCurrentUserStub.called).toBe(true);
@@ -123,14 +134,14 @@ describe('CSV Execute Job', function() {
};
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const job = {
+ const job = getJobDocPayload({
headers: encryptedHeaders,
fields: [],
searchRequest: {
index,
body,
},
- };
+ });
await executeJob('job777', job, cancellationToken);
@@ -152,7 +163,11 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
await executeJob(
'job456',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
@@ -166,7 +181,11 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
await executeJob(
'job456',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
@@ -196,7 +215,11 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
await executeJob(
'job456',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
@@ -231,7 +254,11 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
await executeJob(
'job456',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
@@ -257,12 +284,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: undefined,
searchRequest: { index: null, body: null },
- };
+ });
await expect(
executeJob('job123', jobParams, cancellationToken)
).rejects.toMatchInlineSnapshot(`[TypeError: Cannot read property 'indexOf' of undefined]`);
@@ -284,12 +311,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
'job123',
jobParams,
@@ -309,12 +336,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['=SUM(A1:A2)', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
'job123',
jobParams,
@@ -334,12 +361,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
'job123',
jobParams,
@@ -359,12 +386,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { csv_contains_formulas: csvContainsFormulas } = await executeJob(
'job123',
jobParams,
@@ -386,12 +413,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
expect(content).toEqual(`${CSV_BOM_CHARS}one,two\none,bar\n`);
@@ -407,12 +434,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
expect(content).toEqual('one,two\none,bar\n');
@@ -423,11 +450,11 @@ describe('CSV Execute Job', function() {
it('should reject Promise if search call errors out', async function() {
callAsCurrentUserStub.rejects(new Error());
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
- };
+ });
await expect(
executeJob('job123', jobParams, cancellationToken)
).rejects.toMatchInlineSnapshot(`[Error]`);
@@ -442,11 +469,11 @@ describe('CSV Execute Job', function() {
});
callAsCurrentUserStub.onSecondCall().rejects(new Error());
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
- };
+ });
await expect(
executeJob('job123', jobParams, cancellationToken)
).rejects.toMatchInlineSnapshot(`[Error]`);
@@ -463,11 +490,11 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
- };
+ });
await expect(
executeJob('job123', jobParams, cancellationToken)
).rejects.toMatchInlineSnapshot(
@@ -484,11 +511,11 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
- };
+ });
await expect(
executeJob('job123', jobParams, cancellationToken)
).rejects.toMatchInlineSnapshot(
@@ -512,11 +539,11 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
- };
+ });
await expect(
executeJob('job123', jobParams, cancellationToken)
).rejects.toMatchInlineSnapshot(
@@ -540,11 +567,11 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: [],
searchRequest: { index: null, body: null },
- };
+ });
await expect(
executeJob('job123', jobParams, cancellationToken)
).rejects.toMatchInlineSnapshot(
@@ -578,7 +605,11 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
executeJob(
'job345',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
@@ -593,13 +624,17 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
executeJob(
'job345',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
cancellationToken.cancel();
for (let i = 0; i < callAsCurrentUserStub.callCount; ++i) {
- expect(callAsCurrentUserStub.getCall(i).args[1]).to.not.be('clearScroll');
+ expect(callAsCurrentUserStub.getCall(i).args[1]).not.toBe('clearScroll'); // dead code?
}
});
@@ -607,7 +642,11 @@ describe('CSV Execute Job', function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
executeJob(
'job345',
- { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } },
+ getJobDocPayload({
+ headers: encryptedHeaders,
+ fields: [],
+ searchRequest: { index: null, body: null },
+ }),
cancellationToken
);
await delay(100);
@@ -623,11 +662,11 @@ describe('CSV Execute Job', function() {
describe('csv content', function() {
it('should write column headers to output, even if there are no results', async function() {
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
expect(content).toBe(`one,two\n`);
});
@@ -635,11 +674,11 @@ describe('CSV Execute Job', function() {
it('should use custom uiSettings csv:separator for header', async function() {
mockUiSettingsClient.get.withArgs('csv:separator').returns(';');
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
expect(content).toBe(`one;two\n`);
});
@@ -647,11 +686,11 @@ describe('CSV Execute Job', function() {
it('should escape column headers if uiSettings csv:quoteValues is true', async function() {
mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true);
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one and a half', 'two', 'three-and-four', 'five & six'],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
expect(content).toBe(`"one and a half",two,"three-and-four","five & six"\n`);
});
@@ -659,11 +698,11 @@ describe('CSV Execute Job', function() {
it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function() {
mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(false);
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one and a half', 'two', 'three-and-four', 'five & six'],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
expect(content).toBe(`one and a half,two,three-and-four,five & six\n`);
});
@@ -677,11 +716,11 @@ describe('CSV Execute Job', function() {
_scroll_id: 'scrollId',
});
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
const lines = content.split('\n');
const headerLine = lines[0];
@@ -697,12 +736,12 @@ describe('CSV Execute Job', function() {
_scroll_id: 'scrollId',
});
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
const lines = content.split('\n');
const valuesLine = lines[1];
@@ -724,12 +763,12 @@ describe('CSV Execute Job', function() {
_scroll_id: 'scrollId',
});
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
const lines = content.split('\n');
@@ -746,7 +785,7 @@ describe('CSV Execute Job', function() {
_scroll_id: 'scrollId',
});
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
@@ -760,7 +799,7 @@ describe('CSV Execute Job', function() {
fieldFormatMap: '{"one":{"id":"string","params":{"transform": "upper"}}}',
},
},
- };
+ });
const { content } = await executeJob('job123', jobParams, cancellationToken);
const lines = content.split('\n');
@@ -774,18 +813,18 @@ describe('CSV Execute Job', function() {
// tests use these 'simple' characters to make the math easier
describe('when only the headers exceed the maxSizeBytes', function() {
- let content;
- let maxSizeReached;
+ let content: string;
+ let maxSizeReached: boolean;
beforeEach(async function() {
configGetStub.withArgs('csv', 'maxSizeBytes').returns(1);
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
- };
+ });
({ content, max_size_reached: maxSizeReached } = await executeJob(
'job123',
@@ -804,18 +843,18 @@ describe('CSV Execute Job', function() {
});
describe('when headers are equal to maxSizeBytes', function() {
- let content;
- let maxSizeReached;
+ let content: string;
+ let maxSizeReached: boolean;
beforeEach(async function() {
configGetStub.withArgs('csv', 'maxSizeBytes').returns(9);
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
searchRequest: { index: null, body: null },
- };
+ });
({ content, max_size_reached: maxSizeReached } = await executeJob(
'job123',
@@ -834,8 +873,8 @@ describe('CSV Execute Job', function() {
});
describe('when the data exceeds the maxSizeBytes', function() {
- let content;
- let maxSizeReached;
+ let content: string;
+ let maxSizeReached: boolean;
beforeEach(async function() {
configGetStub.withArgs('csv', 'maxSizeBytes').returns(9);
@@ -848,12 +887,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
({ content, max_size_reached: maxSizeReached } = await executeJob(
'job123',
@@ -872,8 +911,8 @@ describe('CSV Execute Job', function() {
});
describe('when headers and data equal the maxSizeBytes', function() {
- let content;
- let maxSizeReached;
+ let content: string;
+ let maxSizeReached: boolean;
beforeEach(async function() {
mockReportingPlugin.getUiSettingsServiceFactory = () => mockUiSettingsClient;
@@ -887,12 +926,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
({ content, max_size_reached: maxSizeReached } = await executeJob(
'job123',
@@ -924,12 +963,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
await executeJob('job123', jobParams, cancellationToken);
@@ -950,12 +989,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
await executeJob('job123', jobParams, cancellationToken);
@@ -976,12 +1015,12 @@ describe('CSV Execute Job', function() {
});
const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger);
- const jobParams = {
+ const jobParams = getJobDocPayload({
headers: encryptedHeaders,
fields: ['one', 'two'],
conflictedTypesFields: [],
searchRequest: { index: null, body: null },
- };
+ });
await executeJob('job123', jobParams, cancellationToken);
diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts
similarity index 58%
rename from x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js
rename to x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts
index cb63e7dad2fdf4..c9cba64a732b6f 100644
--- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js
+++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts
@@ -5,19 +5,22 @@
*/
import * as Rx from 'rxjs';
-import { createMockReportingCore } from '../../../../test_helpers';
+import { createMockReportingCore, createMockBrowserDriverFactory } from '../../../../test_helpers';
import { cryptoFactory } from '../../../../server/lib/crypto';
import { executeJobFactory } from './index';
import { generatePngObservableFactory } from '../lib/generate_png';
+import { CancellationToken } from '../../../../common/cancellation_token';
import { LevelLogger } from '../../../../server/lib';
+import { ReportingCore, CaptureConfig } from '../../../../server/types';
+import { JobDocPayloadPNG } from '../../types';
jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() }));
-let mockReporting;
+let mockReporting: ReportingCore;
-const cancellationToken = {
+const cancellationToken = ({
on: jest.fn(),
-};
+} as unknown) as CancellationToken;
const mockLoggerFactory = {
get: jest.fn().mockImplementation(() => ({
@@ -28,12 +31,16 @@ const mockLoggerFactory = {
};
const getMockLogger = () => new LevelLogger(mockLoggerFactory);
+const captureConfig = {} as CaptureConfig;
+
const mockEncryptionKey = 'abcabcsecuresecret';
-const encryptHeaders = async headers => {
+const encryptHeaders = async (headers: Record) => {
const crypto = cryptoFactory(mockEncryptionKey);
return await crypto.encrypt(headers);
};
+const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPNG;
+
beforeEach(async () => {
const kbnConfig = {
'server.basePath': '/sbp',
@@ -45,8 +52,8 @@ beforeEach(async () => {
'kibanaServer.protocol': 'http',
};
const mockReportingConfig = {
- get: (...keys) => reportingConfig[keys.join('.')],
- kbnConfig: { get: (...keys) => kbnConfig[keys.join('.')] },
+ get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')],
+ kbnConfig: { get: (...keys: string[]) => (kbnConfig as any)[keys.join('.')] },
};
mockReporting = await createMockReportingCore(mockReportingConfig);
@@ -60,22 +67,30 @@ beforeEach(async () => {
mockGetElasticsearch.mockImplementation(() => Promise.resolve(mockElasticsearch));
mockReporting.getElasticsearchService = mockGetElasticsearch;
- generatePngObservableFactory.mockReturnValue(jest.fn());
+ (generatePngObservableFactory as jest.Mock).mockReturnValue(jest.fn());
});
-afterEach(() => generatePngObservableFactory.mockReset());
+afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset());
test(`passes browserTimezone to generatePng`, async () => {
const encryptedHeaders = await encryptHeaders({});
+ const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger());
- const generatePngObservable = generatePngObservableFactory();
- generatePngObservable.mockReturnValue(Rx.of(Buffer.from('')));
+ const generatePngObservable = generatePngObservableFactory(
+ captureConfig,
+ mockBrowserDriverFactory
+ );
+ (generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
const executeJob = await executeJobFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC';
await executeJob(
'pngJobId',
- { relativeUrl: '/app/kibana#/something', browserTimezone, headers: encryptedHeaders },
+ getJobDocPayload({
+ relativeUrl: '/app/kibana#/something',
+ browserTimezone,
+ headers: encryptedHeaders,
+ }),
cancellationToken
);
@@ -92,12 +107,17 @@ test(`returns content_type of application/png`, async () => {
const executeJob = await executeJobFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
- const generatePngObservable = generatePngObservableFactory();
- generatePngObservable.mockReturnValue(Rx.of(Buffer.from('')));
+ const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger());
+
+ const generatePngObservable = generatePngObservableFactory(
+ captureConfig,
+ mockBrowserDriverFactory
+ );
+ (generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
const { content_type: contentType } = await executeJob(
'pngJobId',
- { relativeUrl: '/app/kibana#/something', timeRange: {}, headers: encryptedHeaders },
+ getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }),
cancellationToken
);
expect(contentType).toBe('image/png');
@@ -106,14 +126,19 @@ test(`returns content_type of application/png`, async () => {
test(`returns content of generatePng getBuffer base64 encoded`, async () => {
const testContent = 'test content';
- const generatePngObservable = generatePngObservableFactory();
- generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
+ const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger());
+
+ const generatePngObservable = generatePngObservableFactory(
+ captureConfig,
+ mockBrowserDriverFactory
+ );
+ (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const executeJob = await executeJobFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
const { content } = await executeJob(
'pngJobId',
- { relativeUrl: '/app/kibana#/something', timeRange: {}, headers: encryptedHeaders },
+ getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }),
cancellationToken
);
diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts
similarity index 57%
rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js
rename to x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts
index c6f07f8ad2d344..c3c0d38584bc13 100644
--- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js
+++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts
@@ -5,19 +5,24 @@
*/
import * as Rx from 'rxjs';
-import { createMockReportingCore } from '../../../../test_helpers';
+import { createMockReportingCore, createMockBrowserDriverFactory } from '../../../../test_helpers';
import { cryptoFactory } from '../../../../server/lib/crypto';
-import { executeJobFactory } from './index';
-import { generatePdfObservableFactory } from '../lib/generate_pdf';
import { LevelLogger } from '../../../../server/lib';
+import { CancellationToken } from '../../../../types';
+import { ReportingCore, CaptureConfig } from '../../../../server/types';
+import { generatePdfObservableFactory } from '../lib/generate_pdf';
+import { JobDocPayloadPDF } from '../../types';
+import { executeJobFactory } from './index';
jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() }));
-let mockReporting;
+let mockReporting: ReportingCore;
-const cancellationToken = {
+const cancellationToken = ({
on: jest.fn(),
-};
+} as unknown) as CancellationToken;
+
+const captureConfig = {} as CaptureConfig;
const mockLoggerFactory = {
get: jest.fn().mockImplementation(() => ({
@@ -29,11 +34,13 @@ const mockLoggerFactory = {
const getMockLogger = () => new LevelLogger(mockLoggerFactory);
const mockEncryptionKey = 'testencryptionkey';
-const encryptHeaders = async headers => {
+const encryptHeaders = async (headers: Record) => {
const crypto = cryptoFactory(mockEncryptionKey);
return await crypto.encrypt(headers);
};
+const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPDF;
+
beforeEach(async () => {
const kbnConfig = {
'server.basePath': '/sbp',
@@ -45,8 +52,8 @@ beforeEach(async () => {
'kibanaServer.protocol': 'http',
};
const mockReportingConfig = {
- get: (...keys) => reportingConfig[keys.join('.')],
- kbnConfig: { get: (...keys) => kbnConfig[keys.join('.')] },
+ get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')],
+ kbnConfig: { get: (...keys: string[]) => (kbnConfig as any)[keys.join('.')] },
};
mockReporting = await createMockReportingCore(mockReportingConfig);
@@ -60,21 +67,26 @@ beforeEach(async () => {
mockGetElasticsearch.mockImplementation(() => Promise.resolve(mockElasticsearch));
mockReporting.getElasticsearchService = mockGetElasticsearch;
- generatePdfObservableFactory.mockReturnValue(jest.fn());
+ (generatePdfObservableFactory as jest.Mock).mockReturnValue(jest.fn());
});
-afterEach(() => generatePdfObservableFactory.mockReset());
+afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset());
test(`returns content_type of application/pdf`, async () => {
- const executeJob = await executeJobFactory(mockReporting, getMockLogger());
+ const logger = getMockLogger();
+ const executeJob = await executeJobFactory(mockReporting, logger);
+ const mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger);
const encryptedHeaders = await encryptHeaders({});
- const generatePdfObservable = generatePdfObservableFactory();
- generatePdfObservable.mockReturnValue(Rx.of(Buffer.from('')));
+ const generatePdfObservable = generatePdfObservableFactory(
+ captureConfig,
+ mockBrowserDriverFactory
+ );
+ (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
const { content_type: contentType } = await executeJob(
'pdfJobId',
- { relativeUrls: [], timeRange: {}, headers: encryptedHeaders },
+ getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }),
cancellationToken
);
expect(contentType).toBe('application/pdf');
@@ -82,15 +94,19 @@ test(`returns content_type of application/pdf`, async () => {
test(`returns content of generatePdf getBuffer base64 encoded`, async () => {
const testContent = 'test content';
+ const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger());
- const generatePdfObservable = generatePdfObservableFactory();
- generatePdfObservable.mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
+ const generatePdfObservable = generatePdfObservableFactory(
+ captureConfig,
+ mockBrowserDriverFactory
+ );
+ (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const executeJob = await executeJobFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
const { content } = await executeJob(
'pdfJobId',
- { relativeUrls: [], timeRange: {}, headers: encryptedHeaders },
+ getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }),
cancellationToken
);
diff --git a/x-pack/legacy/plugins/reporting/index.test.js b/x-pack/legacy/plugins/reporting/index.test.ts
similarity index 94%
rename from x-pack/legacy/plugins/reporting/index.test.js
rename to x-pack/legacy/plugins/reporting/index.test.ts
index 0d9a717bd7d816..8148adab678744 100644
--- a/x-pack/legacy/plugins/reporting/index.test.js
+++ b/x-pack/legacy/plugins/reporting/index.test.ts
@@ -27,7 +27,7 @@ const describeWithContext = describe.each([
describeWithContext('config schema with context %j', context => {
it('produces correct config', async () => {
const schema = await getConfigSchema(reporting);
- const value = await schema.validate({}, { context });
+ const value: any = await schema.validate({}, { context });
value.capture.browser.chromium.disableSandbox = '';
await expect(value).toMatchSnapshot();
});
diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts
similarity index 91%
rename from x-pack/legacy/plugins/reporting/server/routes/jobs.test.js
rename to x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts
index 9f0de844df3699..5c58a7dfa01109 100644
--- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js
+++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts
@@ -6,7 +6,10 @@
import Hapi from 'hapi';
import { createMockReportingCore } from '../../test_helpers';
+import { ExportTypeDefinition } from '../../types';
import { ExportTypesRegistry } from '../lib/export_types_registry';
+import { LevelLogger } from '../lib/level_logger';
+import { ReportingConfig, ReportingCore, ReportingSetupDeps } from '../types';
jest.mock('./lib/authorized_user_pre_routing', () => ({
authorizedUserPreRoutingFactory: () => () => ({}),
@@ -19,14 +22,14 @@ jest.mock('./lib/reporting_feature_pre_routing', () => ({
import { registerJobInfoRoutes } from './jobs';
-let mockServer;
-let exportTypesRegistry;
-let mockReportingPlugin;
-let mockReportingConfig;
-const mockLogger = {
+let mockServer: any;
+let exportTypesRegistry: ExportTypesRegistry;
+let mockReportingPlugin: ReportingCore;
+let mockReportingConfig: ReportingConfig;
+const mockLogger = ({
error: jest.fn(),
debug: jest.fn(),
-};
+} as unknown) as LevelLogger;
beforeEach(async () => {
mockServer = new Hapi.Server({ debug: false, port: 8080, routes: { log: { collect: true } } });
@@ -35,38 +38,39 @@ beforeEach(async () => {
id: 'unencoded',
jobType: 'unencodedJobType',
jobContentExtension: 'csv',
- });
+ } as ExportTypeDefinition);
exportTypesRegistry.register({
id: 'base64Encoded',
jobType: 'base64EncodedJobType',
jobContentEncoding: 'base64',
jobContentExtension: 'pdf',
- });
+ } as ExportTypeDefinition);
mockReportingConfig = { get: jest.fn(), kbnConfig: { get: jest.fn() } };
mockReportingPlugin = await createMockReportingCore(mockReportingConfig);
mockReportingPlugin.getExportTypesRegistry = () => exportTypesRegistry;
});
-const mockPlugins = {
+const mockPlugins = ({
elasticsearch: {
adminClient: { callAsInternalUser: jest.fn() },
},
security: null,
-};
+} as unknown) as ReportingSetupDeps;
-const getHits = (...sources) => {
+const getHits = (...sources: any) => {
return {
hits: {
- hits: sources.map(source => ({ _source: source })),
+ hits: sources.map((source: object) => ({ _source: source })),
},
};
};
-const getErrorsFromRequest = request =>
- request.logs.filter(log => log.tags.includes('error')).map(log => log.error);
+const getErrorsFromRequest = (request: any) =>
+ request.logs.filter((log: any) => log.tags.includes('error')).map((log: any) => log.error);
test(`returns 404 if job not found`, async () => {
+ // @ts-ignore
mockPlugins.elasticsearch.adminClient = {
callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())),
};
@@ -84,6 +88,7 @@ test(`returns 404 if job not found`, async () => {
});
test(`returns 401 if not valid job type`, async () => {
+ // @ts-ignore
mockPlugins.elasticsearch.adminClient = {
callAsInternalUser: jest
.fn()
@@ -103,6 +108,7 @@ test(`returns 401 if not valid job type`, async () => {
describe(`when job is incomplete`, () => {
const getIncompleteResponse = async () => {
+ // @ts-ignore
mockPlugins.elasticsearch.adminClient = {
callAsInternalUser: jest
.fn()
@@ -149,6 +155,7 @@ describe(`when job is failed`, () => {
status: 'failed',
output: { content: 'job failure message' },
});
+ // @ts-ignore
mockPlugins.elasticsearch.adminClient = {
callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)),
};
@@ -194,6 +201,7 @@ describe(`when job is completed`, () => {
title,
},
});
+ // @ts-ignore
mockPlugins.elasticsearch.adminClient = {
callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)),
};
diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts
similarity index 90%
rename from x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js
rename to x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts
index 929109e66914d9..dbc674ce36ec85 100644
--- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js
+++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts
@@ -11,18 +11,21 @@ import {
registerReportingUsageCollector,
getReportingUsageCollector,
} from './reporting_usage_collector';
+import { ReportingConfig } from '../types';
const exportTypesRegistry = getExportTypesRegistry();
function getMockUsageCollection() {
class MockUsageCollector {
- constructor(_server, { fetch }) {
+ // @ts-ignore fetch is not used
+ private fetch: any;
+ constructor(_server: any, { fetch }: any) {
this.fetch = fetch;
}
}
return {
- makeUsageCollector: options => {
- return new MockUsageCollector(this, options);
+ makeUsageCollector: (options: any) => {
+ return new MockUsageCollector(null, options);
},
registerCollector: sinon.stub(),
};
@@ -51,7 +54,7 @@ function getPluginsMock(
xpack_main: mockXpackMain,
},
},
- };
+ } as any;
}
const getMockReportingConfig = () => ({
@@ -61,13 +64,13 @@ const getMockReportingConfig = () => ({
const getResponseMock = (customization = {}) => customization;
describe('license checks', () => {
- let mockConfig;
+ let mockConfig: ReportingConfig;
beforeAll(async () => {
mockConfig = getMockReportingConfig();
});
describe('with a basic license', () => {
- let usageStats;
+ let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'basic' });
const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock()));
@@ -75,9 +78,12 @@ describe('license checks', () => {
mockConfig,
plugins.usageCollection,
plugins.__LEGACY.plugins.xpack_main.info,
- exportTypesRegistry
+ exportTypesRegistry,
+ function isReady() {
+ return Promise.resolve(true);
+ }
);
- usageStats = await fetch(callClusterMock, exportTypesRegistry);
+ usageStats = await fetch(callClusterMock as any);
});
test('sets enables to true', async () => {
@@ -94,7 +100,7 @@ describe('license checks', () => {
});
describe('with no license', () => {
- let usageStats;
+ let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'none' });
const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock()));
@@ -102,9 +108,12 @@ describe('license checks', () => {
mockConfig,
plugins.usageCollection,
plugins.__LEGACY.plugins.xpack_main.info,
- exportTypesRegistry
+ exportTypesRegistry,
+ function isReady() {
+ return Promise.resolve(true);
+ }
);
- usageStats = await fetch(callClusterMock, exportTypesRegistry);
+ usageStats = await fetch(callClusterMock as any);
});
test('sets enables to true', async () => {
@@ -121,7 +130,7 @@ describe('license checks', () => {
});
describe('with platinum license', () => {
- let usageStats;
+ let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'platinum' });
const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock()));
@@ -129,9 +138,12 @@ describe('license checks', () => {
mockConfig,
plugins.usageCollection,
plugins.__LEGACY.plugins.xpack_main.info,
- exportTypesRegistry
+ exportTypesRegistry,
+ function isReady() {
+ return Promise.resolve(true);
+ }
);
- usageStats = await fetch(callClusterMock, exportTypesRegistry);
+ usageStats = await fetch(callClusterMock as any);
});
test('sets enables to true', async () => {
@@ -148,7 +160,7 @@ describe('license checks', () => {
});
describe('with no usage data', () => {
- let usageStats;
+ let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'basic' });
const callClusterMock = jest.fn(() => Promise.resolve({}));
@@ -156,9 +168,12 @@ describe('license checks', () => {
mockConfig,
plugins.usageCollection,
plugins.__LEGACY.plugins.xpack_main.info,
- exportTypesRegistry
+ exportTypesRegistry,
+ function isReady() {
+ return Promise.resolve(true);
+ }
);
- usageStats = await fetch(callClusterMock, exportTypesRegistry);
+ usageStats = await fetch(callClusterMock as any);
});
test('sets enables to true', async () => {
@@ -179,7 +194,10 @@ describe('data modeling', () => {
mockConfig,
plugins.usageCollection,
plugins.__LEGACY.plugins.xpack_main.info,
- exportTypesRegistry
+ exportTypesRegistry,
+ function isReady() {
+ return Promise.resolve(true);
+ }
);
const callClusterMock = jest.fn(() =>
Promise.resolve(
@@ -303,7 +321,7 @@ describe('data modeling', () => {
)
);
- const usageStats = await fetch(callClusterMock);
+ const usageStats = await fetch(callClusterMock as any);
expect(usageStats).toMatchInlineSnapshot(`
Object {
"PNG": Object {
@@ -406,7 +424,7 @@ describe('Ready for collection observable', () => {
const makeCollectorSpy = sinon.spy();
usageCollection.makeUsageCollector = makeCollectorSpy;
- const plugins = getPluginsMock({ usageCollection });
+ const plugins = getPluginsMock({ usageCollection } as any);
registerReportingUsageCollector(mockReporting, plugins);
const [args] = makeCollectorSpy.firstCall.args;
diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts
index 930aa7601b8cbc..6e95bed2ecf927 100644
--- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts
+++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts
@@ -92,7 +92,7 @@ const defaultOpts: CreateMockBrowserDriverFactoryOpts = {
export const createMockBrowserDriverFactory = async (
logger: Logger,
- opts: Partial
+ opts: Partial = {}
): Promise => {
const captureConfig = {
timeouts: { openUrl: 30000, waitForElements: 30000, renderComplete: 30000 },
diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts
index 7334a859005e0c..eec7da7dc67333 100644
--- a/x-pack/legacy/plugins/reporting/types.d.ts
+++ b/x-pack/legacy/plugins/reporting/types.d.ts
@@ -186,7 +186,7 @@ export type ESQueueWorkerExecuteFn = (
jobId: string,
job: JobDocPayloadType,
cancellationToken?: CancellationToken
-) => void;
+) => Promise;
/*
* ImmediateExecuteFn receives the job doc payload because the payload was
diff --git a/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx b/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx
index 3c01ec18a879f7..fca6396a537455 100644
--- a/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx
@@ -89,6 +89,7 @@ export const FilterPopoverComponent = ({
{options.map((option, index) => (
diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx
index 165be003847794..0c6f7258d09dc7 100644
--- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx
@@ -60,12 +60,9 @@ const EditableTitleComponent: React.FC = ({
}, [changedTitle, title]);
const handleOnChange = useCallback(
- (e: ChangeEvent) => {
- onTitleChange(e.target.value);
- },
- [onTitleChange]
+ (e: ChangeEvent) => onTitleChange(e.target.value),
+ []
);
-
return editMode ? (
@@ -107,7 +104,7 @@ const EditableTitleComponent: React.FC = ({
- {isLoading && }
+ {isLoading && }
{!isLoading && (
(decodeCaseResponse(response));
};
-export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promise => {
+export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promise => {
const response = await KibanaServices.get().http.fetch(CASES_URL, {
method: 'DELETE',
query: { ids: JSON.stringify(caseIds) },
signal,
});
- return response === 'true' ? true : false;
+ return response;
};
export const pushCase = async (
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
index 7f57149d4e56dd..1c03a09a8c2eaf 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
@@ -55,7 +55,6 @@ export const useCaseConfigure = ({
setLoading(true);
const res = await getCaseConfigure({ signal: abortCtrl.signal });
if (!didCancel) {
- setLoading(false);
if (res != null) {
setConnector(res.connectorId, res.connectorName);
if (setClosureType != null) {
@@ -73,6 +72,7 @@ export const useCaseConfigure = ({
}
}
}
+ setLoading(false);
}
} catch (error) {
if (!didCancel) {
@@ -117,7 +117,6 @@ export const useCaseConfigure = ({
abortCtrl.signal
);
if (!didCancel) {
- setPersistLoading(false);
setConnector(res.connectorId);
if (setClosureType) {
setClosureType(res.closureType);
@@ -131,6 +130,7 @@ export const useCaseConfigure = ({
}
displaySuccessToast(i18n.SUCCESS_CONFIGURE, dispatchToaster);
+ setPersistLoading(false);
}
} catch (error) {
if (!didCancel) {
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
index 2ae35796387b82..aefb0a93366b8f 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
@@ -11,11 +11,9 @@ import { useGetUserSavedObjectPermissions } from '../../lib/kibana';
import { SpyRoute } from '../../utils/route/spy_routes';
import { AllCases } from './components/all_cases';
-import { getSavedObjectReadOnly, CaseCallOut } from './components/callout';
+import { savedObjectReadOnly, CaseCallOut } from './components/callout';
import { CaseSavedObjectNoPermissions } from './saved_object_no_permissions';
-const infoReadSavedObject = getSavedObjectReadOnly();
-
export const CasesPage = React.memo(() => {
const userPermissions = useGetUserSavedObjectPermissions();
@@ -24,10 +22,11 @@ export const CasesPage = React.memo(() => {
{userPermissions != null && !userPermissions?.crud && userPermissions?.read && (
)}
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx
index cbc7bbc62fbf90..4bb8afa7f8d42a 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx
@@ -13,9 +13,7 @@ import { SpyRoute } from '../../utils/route/spy_routes';
import { getCaseUrl } from '../../components/link_to';
import { navTabs } from '../home/home_navigations';
import { CaseView } from './components/case_view';
-import { getSavedObjectReadOnly, CaseCallOut } from './components/callout';
-
-const infoReadSavedObject = getSavedObjectReadOnly();
+import { savedObjectReadOnly, CaseCallOut } from './components/callout';
export const CaseDetailsPage = React.memo(() => {
const userPermissions = useGetUserSavedObjectPermissions();
@@ -29,7 +27,7 @@ export const CaseDetailsPage = React.memo(() => {
return caseId != null ? (
<>
{userPermissions != null && !userPermissions?.crud && userPermissions?.read && (
-
+
)}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx
new file mode 100644
index 00000000000000..64c6276fc1be2b
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx
@@ -0,0 +1,226 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { CaseProps } from '../case_view';
+import { Case, Comment, SortFieldCase } from '../../../../containers/case/types';
+import { UseGetCasesState } from '../../../../containers/case/use_get_cases';
+import { UserAction, UserActionField } from '../../../../../../../../plugins/case/common/api/cases';
+
+const updateCase = jest.fn();
+const fetchCase = jest.fn();
+
+const basicCaseId = 'basic-case-id';
+const basicCommentId = 'basic-comment-id';
+const basicCreatedAt = '2020-02-20T23:06:33.798Z';
+const elasticUser = {
+ fullName: 'Leslie Knope',
+ username: 'lknope',
+ email: 'leslie.knope@elastic.co',
+};
+
+export const basicComment: Comment = {
+ comment: 'Solve this fast!',
+ id: basicCommentId,
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ pushedAt: null,
+ pushedBy: null,
+ updatedAt: '2020-02-20T23:06:33.798Z',
+ updatedBy: {
+ username: 'elastic',
+ },
+ version: 'WzQ3LDFc',
+};
+
+export const basicCase: Case = {
+ closedAt: null,
+ closedBy: null,
+ id: basicCaseId,
+ comments: [basicComment],
+ createdAt: '2020-02-13T19:44:23.627Z',
+ createdBy: elasticUser,
+ description: 'Security banana Issue',
+ externalService: null,
+ status: 'open',
+ tags: ['defacement'],
+ title: 'Another horrible breach!!',
+ totalComment: 1,
+ updatedAt: '2020-02-19T15:02:57.995Z',
+ updatedBy: {
+ username: 'elastic',
+ },
+ version: 'WzQ3LDFd',
+};
+
+export const caseProps: CaseProps = {
+ caseId: basicCaseId,
+ userCanCrud: true,
+ caseData: basicCase,
+ fetchCase,
+ updateCase,
+};
+
+export const caseClosedProps: CaseProps = {
+ ...caseProps,
+ caseData: {
+ ...caseProps.caseData,
+ closedAt: '2020-02-20T23:06:33.798Z',
+ closedBy: {
+ username: 'elastic',
+ },
+ status: 'closed',
+ },
+};
+
+export const basicCaseClosed: Case = {
+ ...caseClosedProps.caseData,
+};
+
+const basicAction = {
+ actionAt: basicCreatedAt,
+ actionBy: elasticUser,
+ oldValue: null,
+ newValue: 'what a cool value',
+ caseId: basicCaseId,
+ commentId: null,
+};
+export const caseUserActions = [
+ {
+ ...basicAction,
+ actionBy: elasticUser,
+ actionField: ['comment'],
+ action: 'create',
+ actionId: 'tt',
+ },
+];
+
+export const useGetCasesMockState: UseGetCasesState = {
+ data: {
+ countClosedCases: 0,
+ countOpenCases: 5,
+ cases: [
+ basicCase,
+ {
+ closedAt: null,
+ closedBy: null,
+ id: '362a5c10-4e99-11ea-9290-35d05cb55c15',
+ createdAt: '2020-02-13T19:44:13.328Z',
+ createdBy: { username: 'elastic' },
+ comments: [],
+ description: 'Security banana Issue',
+ externalService: {
+ pushedAt: '2020-02-13T19:45:01.901Z',
+ pushedBy: 'elastic',
+ connectorId: 'string',
+ connectorName: 'string',
+ externalId: 'string',
+ externalTitle: 'string',
+ externalUrl: 'string',
+ },
+ status: 'open',
+ tags: ['phishing'],
+ title: 'Bad email',
+ totalComment: 0,
+ updatedAt: '2020-02-13T15:45:01.901Z',
+ updatedBy: { username: 'elastic' },
+ version: 'WzQ3LDFd',
+ },
+ {
+ closedAt: null,
+ closedBy: null,
+ id: '34f8b9e0-4e99-11ea-9290-35d05cb55c15',
+ createdAt: '2020-02-13T19:44:11.328Z',
+ createdBy: { username: 'elastic' },
+ comments: [],
+ description: 'Security banana Issue',
+ externalService: {
+ pushedAt: '2020-02-13T19:45:01.901Z',
+ pushedBy: 'elastic',
+ connectorId: 'string',
+ connectorName: 'string',
+ externalId: 'string',
+ externalTitle: 'string',
+ externalUrl: 'string',
+ },
+ status: 'open',
+ tags: ['phishing'],
+ title: 'Bad email',
+ totalComment: 0,
+ updatedAt: '2020-02-14T19:45:01.901Z',
+ updatedBy: { username: 'elastic' },
+ version: 'WzQ3LDFd',
+ },
+ {
+ closedAt: '2020-02-13T19:44:13.328Z',
+ closedBy: { username: 'elastic' },
+ id: '31890e90-4e99-11ea-9290-35d05cb55c15',
+ createdAt: '2020-02-13T19:44:05.563Z',
+ createdBy: { username: 'elastic' },
+ comments: [],
+ description: 'Security banana Issue',
+ externalService: null,
+ status: 'closed',
+ tags: ['phishing'],
+ title: 'Uh oh',
+ totalComment: 0,
+ updatedAt: null,
+ updatedBy: null,
+ version: 'WzQ3LDFd',
+ },
+ {
+ closedAt: null,
+ closedBy: null,
+ id: '2f5b3210-4e99-11ea-9290-35d05cb55c15',
+ createdAt: '2020-02-13T19:44:01.901Z',
+ createdBy: { username: 'elastic' },
+ comments: [],
+ description: 'Security banana Issue',
+ externalService: null,
+ status: 'open',
+ tags: ['phishing'],
+ title: 'Uh oh',
+ totalComment: 0,
+ updatedAt: null,
+ updatedBy: null,
+ version: 'WzQ3LDFd',
+ },
+ ],
+ page: 1,
+ perPage: 5,
+ total: 10,
+ },
+ loading: [],
+ selectedCases: [],
+ isError: false,
+ queryParams: {
+ page: 1,
+ perPage: 5,
+ sortField: SortFieldCase.createdAt,
+ sortOrder: 'desc',
+ },
+ filterOptions: { search: '', reporters: [], tags: [], status: 'open' },
+};
+
+const basicPush = {
+ connector_id: 'connector_id',
+ connector_name: 'connector name',
+ external_id: 'external_id',
+ external_title: 'external title',
+ external_url: 'basicPush.com',
+ pushed_at: basicCreatedAt,
+ pushed_by: elasticUser,
+};
+export const getUserAction = (af: UserActionField, a: UserAction) => ({
+ ...basicAction,
+ actionId: `${af[0]}-${a}`,
+ actionField: af,
+ action: a,
+ commentId: af[0] === 'comment' ? basicCommentId : null,
+ newValue:
+ a === 'push-to-service' && af[0] === 'pushed'
+ ? JSON.stringify(basicPush)
+ : basicAction.newValue,
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/form.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/form.ts
new file mode 100644
index 00000000000000..9d2ac29bc47d7d
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/form.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+export const mockFormHook = {
+ isSubmitted: false,
+ isSubmitting: false,
+ isValid: true,
+ submit: jest.fn(),
+ subscribe: jest.fn(),
+ setFieldValue: jest.fn(),
+ setFieldErrors: jest.fn(),
+ getFields: jest.fn(),
+ getFormData: jest.fn(),
+ getFieldDefaultValue: jest.fn(),
+ /* Returns a list of all errors in the form */
+ getErrors: jest.fn(),
+ reset: jest.fn(),
+ __options: {},
+ __formData$: {},
+ __addField: jest.fn(),
+ __removeField: jest.fn(),
+ __validateFields: jest.fn(),
+ __updateFormDataAt: jest.fn(),
+ __readFieldConfigFromSchema: jest.fn(),
+};
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const getFormMock = (sampleData: any) => ({
+ ...mockFormHook,
+ submit: () =>
+ Promise.resolve({
+ data: sampleData,
+ isValid: true,
+ }),
+ getFormData: () => sampleData,
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/router.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/router.ts
new file mode 100644
index 00000000000000..a20ab00852a360
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/router.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Router } from 'react-router-dom';
+// eslint-disable-next-line @kbn/eslint/module_migration
+import routeData from 'react-router';
+type Action = 'PUSH' | 'POP' | 'REPLACE';
+const pop: Action = 'POP';
+const location = {
+ pathname: '/network',
+ search: '',
+ state: '',
+ hash: '',
+};
+export const mockHistory = {
+ length: 2,
+ location,
+ action: pop,
+ push: jest.fn(),
+ replace: jest.fn(),
+ go: jest.fn(),
+ goBack: jest.fn(),
+ goForward: jest.fn(),
+ block: jest.fn(),
+ createHref: jest.fn(),
+ listen: jest.fn(),
+};
+
+export const mockLocation = {
+ pathname: '/welcome',
+ hash: '',
+ search: '',
+ state: '',
+};
+
+export { Router, routeData };
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.test.tsx
new file mode 100644
index 00000000000000..74f6411f17fa06
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.test.tsx
@@ -0,0 +1,144 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+
+import { AddComment } from './';
+import { TestProviders } from '../../../../mock';
+import { getFormMock } from '../__mock__/form';
+import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
+
+import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline';
+import { usePostComment } from '../../../../containers/case/use_post_comment';
+import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form';
+import { wait } from '../../../../lib/helpers';
+jest.mock(
+ '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
+);
+jest.mock('../../../../components/timeline/insert_timeline_popover/use_insert_timeline');
+jest.mock('../../../../containers/case/use_post_comment');
+
+export const useFormMock = useForm as jest.Mock;
+
+const useInsertTimelineMock = useInsertTimeline as jest.Mock;
+const usePostCommentMock = usePostComment as jest.Mock;
+
+const onCommentSaving = jest.fn();
+const onCommentPosted = jest.fn();
+const postComment = jest.fn();
+const handleCursorChange = jest.fn();
+const handleOnTimelineChange = jest.fn();
+
+const addCommentProps = {
+ caseId: '1234',
+ disabled: false,
+ insertQuote: null,
+ onCommentSaving,
+ onCommentPosted,
+ showLoading: false,
+};
+
+const defaultInsertTimeline = {
+ cursorPosition: {
+ start: 0,
+ end: 0,
+ },
+ handleCursorChange,
+ handleOnTimelineChange,
+};
+
+const defaultPostCommment = {
+ isLoading: false,
+ isError: false,
+ postComment,
+};
+const sampleData = {
+ comment: 'what a cool comment',
+};
+describe('AddComment ', () => {
+ const formHookMock = getFormMock(sampleData);
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline);
+ usePostCommentMock.mockImplementation(() => defaultPostCommment);
+ useFormMock.mockImplementation(() => ({ form: formHookMock }));
+ jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
+ });
+
+ it('should post comment on submit click', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+ expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy();
+ expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy();
+
+ wrapper
+ .find(`[data-test-subj="submit-comment"]`)
+ .first()
+ .simulate('click');
+ await wait();
+ expect(onCommentSaving).toBeCalled();
+ expect(postComment).toBeCalledWith(sampleData, onCommentPosted);
+ expect(formHookMock.reset).toBeCalled();
+ });
+
+ it('should render spinner and disable submit when loading', () => {
+ usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true }));
+ const wrapper = mount(
+
+
+
+
+
+ );
+ expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeTruthy();
+ expect(
+ wrapper
+ .find(`[data-test-subj="submit-comment"]`)
+ .first()
+ .prop('isDisabled')
+ ).toBeTruthy();
+ });
+
+ it('should disable submit button when disabled prop passed', () => {
+ usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true }));
+ const wrapper = mount(
+
+
+
+
+
+ );
+ expect(
+ wrapper
+ .find(`[data-test-subj="submit-comment"]`)
+ .first()
+ .prop('isDisabled')
+ ).toBeTruthy();
+ });
+
+ it('should insert a quote if one is available', () => {
+ const sampleQuote = 'what a cool quote';
+ mount(
+
+
+
+
+
+ );
+
+ expect(formHookMock.setFieldValue).toBeCalledWith(
+ 'comment',
+ `${sampleData.comment}\n\n${sampleQuote}`
+ );
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
index ecc57c50e28ebe..eaba708948a993 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
@@ -71,10 +71,9 @@ export const AddComment = React.memo(
form.reset();
}
}, [form, onCommentPosted, onCommentSaving]);
-
return (
>
)}
@@ -316,7 +320,7 @@ export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => {
return (
-
+
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.test.tsx
new file mode 100644
index 00000000000000..d480744fc932a7
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.test.tsx
@@ -0,0 +1,121 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+
+import { Create } from './';
+import { TestProviders } from '../../../../mock';
+import { getFormMock } from '../__mock__/form';
+import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
+
+import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline';
+import { usePostCase } from '../../../../containers/case/use_post_case';
+jest.mock('../../../../components/timeline/insert_timeline_popover/use_insert_timeline');
+jest.mock('../../../../containers/case/use_post_case');
+import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks';
+import { wait } from '../../../../lib/helpers';
+import { SiemPageName } from '../../../home/types';
+jest.mock(
+ '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
+);
+
+export const useFormMock = useForm as jest.Mock;
+
+const useInsertTimelineMock = useInsertTimeline as jest.Mock;
+const usePostCaseMock = usePostCase as jest.Mock;
+
+const postCase = jest.fn();
+const handleCursorChange = jest.fn();
+const handleOnTimelineChange = jest.fn();
+
+const defaultInsertTimeline = {
+ cursorPosition: {
+ start: 0,
+ end: 0,
+ },
+ handleCursorChange,
+ handleOnTimelineChange,
+};
+const sampleData = {
+ description: 'what a great description',
+ tags: ['coke', 'pepsi'],
+ title: 'what a cool title',
+};
+const defaultPostCase = {
+ isLoading: false,
+ isError: false,
+ caseData: null,
+ postCase,
+};
+describe('Create case', () => {
+ const formHookMock = getFormMock(sampleData);
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline);
+ usePostCaseMock.mockImplementation(() => defaultPostCase);
+ useFormMock.mockImplementation(() => ({ form: formHookMock }));
+ jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
+ });
+
+ it('should post case on submit click', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+ wrapper
+ .find(`[data-test-subj="create-case-submit"]`)
+ .first()
+ .simulate('click');
+ await wait();
+ expect(postCase).toBeCalledWith(sampleData);
+ });
+
+ it('should redirect to all cases on cancel click', () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+ wrapper
+ .find(`[data-test-subj="create-case-cancel"]`)
+ .first()
+ .simulate('click');
+ expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual(`/${SiemPageName.case}`);
+ });
+ it('should redirect to new case when caseData is there', () => {
+ const sampleId = '777777';
+ usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, caseData: { id: sampleId } }));
+ mount(
+
+
+
+
+
+ );
+ expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual(
+ `/${SiemPageName.case}/${sampleId}`
+ );
+ });
+
+ it('should render spinner when loading', () => {
+ usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, isLoading: true }));
+ const wrapper = mount(
+
+
+
+
+
+ );
+ expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy();
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx
index 740909db408ec4..53b792bb9b5ebb 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx
@@ -73,7 +73,7 @@ export const Create = React.memo(() => {
const handleSetIsCancel = useCallback(() => {
setIsCancel(true);
- }, [isCancel]);
+ }, []);
if (caseData != null && caseData.id) {
return ;
@@ -85,7 +85,7 @@ export const Create = React.memo(() => {
return (
- {isLoading && }
+ {isLoading && }
);
+ /* eslint-enable jsx-a11y/click-events-have-key-events */
}
)
)`
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts
index ca6c0b44d5804b..ca987441c7ce9a 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts
@@ -23,10 +23,12 @@ import {
import { defaultColdPhase, defaultWarmPhase, defaultHotPhase } from '../store/defaults';
-export let trackUiMetric: (metricType: UiStatsMetricType, eventName: string) => void;
+export let trackUiMetric = (metricType: UiStatsMetricType, eventName: string) => {};
-export function init(usageCollection: UsageCollectionSetup): void {
- trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, UIM_APP_NAME);
+export function init(usageCollection?: UsageCollectionSetup): void {
+ if (usageCollection) {
+ trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, UIM_APP_NAME);
+ }
}
export function getUiMetricsForPhases(phases: any): any {
diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts
index f9e0abae56cb40..178884a7ee6790 100644
--- a/x-pack/plugins/index_lifecycle_management/public/types.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/types.ts
@@ -9,7 +9,7 @@ import { ManagementSetup } from '../../../../src/plugins/management/public';
import { IndexManagementPluginSetup } from '../../index_management/public';
export interface PluginsDependencies {
- usageCollection: UsageCollectionSetup;
+ usageCollection?: UsageCollectionSetup;
management: ManagementSetup;
indexManagement?: IndexManagementPluginSetup;
}
diff --git a/x-pack/plugins/index_lifecycle_management/server/plugin.ts b/x-pack/plugins/index_lifecycle_management/server/plugin.ts
index 48c50f9a48ee5d..faeac67f62a21c 100644
--- a/x-pack/plugins/index_lifecycle_management/server/plugin.ts
+++ b/x-pack/plugins/index_lifecycle_management/server/plugin.ts
@@ -75,7 +75,7 @@ export class IndexLifecycleManagementServerPlugin implements Plugin
= none) {
- this.claimRequests$.next(task);
+ private attemptToRun(task: string) {
+ this.claimRequests$.next(some(task));
}
private createTaskRunnerForTask = (instance: ConcreteTaskInstance) => {
@@ -280,9 +280,7 @@ export class TaskManager {
...options,
taskInstance: ensureDeprecatedFieldsAreCorrected(taskInstance, this.logger),
});
- const result = await this.store.schedule(modifiedTask);
- this.attemptToRun();
- return result;
+ return await this.store.schedule(modifiedTask);
}
/**
@@ -298,7 +296,7 @@ export class TaskManager {
.then(resolve)
.catch(reject);
- this.attemptToRun(some(taskId));
+ this.attemptToRun(taskId);
});
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c07ec68e99b4f3..fe0c58e83e544a 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -130,6 +130,14 @@
"charts.colormaps.greysText": "グレー",
"charts.colormaps.redsText": "赤",
"charts.colormaps.yellowToRedText": "黄色から赤",
+ "charts.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。",
+ "charts.controls.colorSchema.colorSchemaLabel": "配色",
+ "charts.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。",
+ "charts.controls.colorSchema.resetColorsButtonLabel": "色をリセット",
+ "charts.controls.colorSchema.reverseColorSchemaLabel": "図表を反転",
+ "charts.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません",
+ "charts.controls.vislibBasicOptions.legendPositionLabel": "凡例位置",
+ "charts.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示",
"common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "エラー",
"common.ui.errorAutoCreateIndex.errorDescription": "Elasticsearch クラスターの {autoCreateIndexActionConfig} 設定が原因で、Kibana が保存されたオブジェクトを格納するインデックスを自動的に作成できないようです。Kibana は、保存されたオブジェクトインデックスが適切なマッピング/スキーマを使用し Kibana から Elasticsearch へのポーリングの回数を減らすための最適な手段であるため、この Elasticsearch の機能を使用します。",
"common.ui.errorAutoCreateIndex.errorDisclaimer": "申し訳ございませんが、この問題が解決されるまで Kibana で何も保存することができません。",
@@ -3831,11 +3839,6 @@
"visTypeVislib.chartTypes.areaText": "エリア",
"visTypeVislib.chartTypes.barText": "バー",
"visTypeVislib.chartTypes.lineText": "折れ線",
- "visTypeVislib.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。",
- "visTypeVislib.controls.colorSchema.colorSchemaLabel": "配色",
- "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。",
- "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "色をリセット",
- "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "図表を反転",
"visTypeVislib.controls.gaugeOptions.alignmentLabel": "アラインメント",
"visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張",
"visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "警告を表示",
@@ -3902,10 +3905,7 @@
"visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "カスタム範囲を切り替える",
"visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える",
"visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 軸",
- "visTypeVislib.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません",
"visTypeVislib.controls.truncateLabel": "切り捨て",
- "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "凡例位置",
- "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示",
"visTypeVislib.editors.heatmap.basicSettingsTitle": "基本設定",
"visTypeVislib.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定",
"visTypeVislib.editors.heatmap.highlightLabel": "ハイライト範囲",
@@ -16102,7 +16102,6 @@
"xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "ユーザー名",
"xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。",
"xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "キャンセル",
- "xpack.triggersActionsUI.sections.editConnectorForm.flyoutTitle": "コネクターを編集",
"xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel": "保存",
"xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "コネクターを更新できません。",
"xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText": "「{connectorName}」を更新しました",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index de8aaa75632eec..fd2a92c2c402f6 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -130,6 +130,14 @@
"charts.colormaps.greysText": "灰色",
"charts.colormaps.redsText": "红色",
"charts.colormaps.yellowToRedText": "黄到红",
+ "charts.controls.colorRanges.errorText": "每个范围应大于前一范围。",
+ "charts.controls.colorSchema.colorSchemaLabel": "颜色模式",
+ "charts.controls.colorSchema.howToChangeColorsDescription": "可以更改图例中的各个颜色。",
+ "charts.controls.colorSchema.resetColorsButtonLabel": "重置颜色",
+ "charts.controls.colorSchema.reverseColorSchemaLabel": "反转模式",
+ "charts.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内",
+ "charts.controls.vislibBasicOptions.legendPositionLabel": "图例位置",
+ "charts.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示",
"common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "错误",
"common.ui.errorAutoCreateIndex.errorDescription": "似乎 Elasticsearch 集群的 {autoCreateIndexActionConfig} 设置使 Kibana 无法自动创建用于存储已保存对象的索引。Kibana 将使用此 Elasticsearch 功能,因为这是确保已保存对象索引使用正确映射/架构的最好方式,而且其允许 Kibana 较少地轮询 Elasticsearch。",
"common.ui.errorAutoCreateIndex.errorDisclaimer": "但是,只有解决了此问题后,您才能在 Kibana 保存内容。",
@@ -3832,11 +3840,6 @@
"visTypeVislib.chartTypes.areaText": "面积图",
"visTypeVislib.chartTypes.barText": "条形图",
"visTypeVislib.chartTypes.lineText": "折线图",
- "visTypeVislib.controls.colorRanges.errorText": "每个范围应大于前一范围。",
- "visTypeVislib.controls.colorSchema.colorSchemaLabel": "颜色模式",
- "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "可以更改图例中的各个颜色。",
- "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "重置颜色",
- "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "反转模式",
"visTypeVislib.controls.gaugeOptions.alignmentLabel": "对齐方式",
"visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "自动扩展范围",
"visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "显示警告",
@@ -3903,10 +3906,7 @@
"visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "切换定制范围",
"visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "切换 {axisName} 选项",
"visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 轴",
- "visTypeVislib.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内",
"visTypeVislib.controls.truncateLabel": "截断",
- "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "图例位置",
- "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示",
"visTypeVislib.editors.heatmap.basicSettingsTitle": "基本设置",
"visTypeVislib.editors.heatmap.heatmapSettingsTitle": "热图设置",
"visTypeVislib.editors.heatmap.highlightLabel": "高亮范围",
@@ -16107,7 +16107,6 @@
"xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "用户名",
"xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。",
"xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "取消",
- "xpack.triggersActionsUI.sections.editConnectorForm.flyoutTitle": "编辑连接器",
"xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel": "保存",
"xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "无法更新连接器。",
"xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText": "已更新“{connectorName}”",
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx
index ab9b5c2586c177..957c79a5c51239 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx
@@ -22,9 +22,10 @@ export const AddMessageVariables: React.FunctionComponent = ({
const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState(false);
const getMessageVariables = () =>
- messageVariables?.map((variable: string) => (
+ messageVariables?.map((variable: string, i: number) => (
{
onSelectEventHandler(variable);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx
index 9c51139993b3f3..3fbcd13e98f5d0 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx
@@ -92,7 +92,7 @@ describe('health check', () => {
const description = queryByRole(/banner/i);
expect(description!.textContent).toMatchInlineSnapshot(
- `"To create an alert, set a value for xpack.encrypted_saved_objects.encryptionKey in your kibana.yml file. Learn how."`
+ `"To create an alert, set a value for xpack.encryptedSavedObjects.encryptionKey in your kibana.yml file. Learn how."`
);
const action = queryByText(/Learn/i);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx
index c967cf5de0771c..afd5e08f52f255 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx
@@ -132,7 +132,7 @@ const EncryptionError = ({
defaultMessage: 'To create an alert, set a value for ',
}
)}
- {'xpack.encrypted_saved_objects.encryptionKey'}
+ {'xpack.encryptedSavedObjects.encryptionKey'}
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey',
{
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx
index 6b011ac84bc6f1..5890d9fe07f0ea 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx
@@ -141,15 +141,22 @@ export const ActionForm = ({
});
}
}
+ const preconfiguredMessage = i18n.translate(
+ 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage',
+ {
+ defaultMessage: '(pre-configured)',
+ }
+ );
const getSelectedOptions = (actionItemId: string) => {
const val = connectors.find(connector => connector.id === actionItemId);
if (!val) {
return [];
}
+ const optionTitle = `${val.name} ${val.isPreconfigured ? preconfiguredMessage : ''}`;
return [
{
- label: val.name,
- value: val.name,
+ label: optionTitle,
+ value: optionTitle,
id: actionItemId,
},
];
@@ -264,7 +271,9 @@ export const ActionForm = ({
defaultMessage="{actionConnectorName}"
id="xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeEditTitle"
values={{
- actionConnectorName: actionConnector.name,
+ actionConnectorName: `${actionConnector.name} ${
+ actionConnector.isPreconfigured ? preconfiguredMessage : ''
+ }`,
}}
/>
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx
index 2c063ea6b4fa64..66598887976795 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx
@@ -95,4 +95,60 @@ describe('connector_edit_flyout', () => {
expect(connectorNameField.exists()).toBeTruthy();
expect(connectorNameField.first().prop('value')).toBe('action-connector');
});
+
+ test('if pre-configured connector rendered correct in the edit form', () => {
+ const connector = {
+ secrets: {},
+ id: 'test',
+ actionTypeId: 'test-action-type-id',
+ actionType: 'test-action-type-name',
+ name: 'pre-configured-connector',
+ isPreconfigured: true,
+ referencedByCount: 0,
+ config: {},
+ };
+
+ const actionType = {
+ id: 'test-action-type-id',
+ iconClass: 'test',
+ selectMessage: 'test',
+ validateConnector: (): ValidationResult => {
+ return { errors: {} };
+ },
+ validateParams: (): ValidationResult => {
+ const validationResult = { errors: {} };
+ return validationResult;
+ },
+ actionConnectorFields: null,
+ actionParamsFields: null,
+ };
+ actionTypeRegistry.get.mockReturnValue(actionType);
+ actionTypeRegistry.has.mockReturnValue(true);
+
+ const wrapper = mountWithIntl(
+
+ {
+ return new Promise(() => {});
+ },
+ }}
+ >
+ {}}
+ />
+
+
+ );
+
+ const preconfiguredBadge = wrapper.find('[data-test-subj="preconfiguredBadge"]');
+ expect(preconfiguredBadge.exists()).toBeTruthy();
+ expect(wrapper.find('[data-test-subj="saveEditedActionButton"]').exists()).toBeFalsy();
+ });
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
index ed8811d26331b9..a81d6c285f460b 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, useReducer, useState } from 'react';
+import React, { useCallback, useReducer, useState, Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiTitle,
@@ -17,6 +17,8 @@ import {
EuiButtonEmpty,
EuiButton,
EuiBetaBadge,
+ EuiText,
+ EuiLink,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ActionConnectorForm, validateBaseProperties } from './action_connector_form';
@@ -91,8 +93,77 @@ export const ConnectorEditFlyout = ({
return undefined;
});
+ const flyoutTitle = connector.isPreconfigured ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ );
+
return (
-
+
{actionTypeModel ? (
@@ -100,41 +171,37 @@ export const ConnectorEditFlyout = ({
) : null}
-
-
-
-
-
-
-
-
-
+ {flyoutTitle}
-
+ {!connector.isPreconfigured ? (
+
+ ) : (
+
+
+ {i18n.translate(
+ 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText',
+ {
+ defaultMessage: 'This connector is readonly.',
+ }
+ )}
+
+
+
+
+
+ )}
@@ -148,7 +215,7 @@ export const ConnectorEditFlyout = ({
)}
- {canSave && actionTypeModel ? (
+ {canSave && actionTypeModel && !connector.isPreconfigured ? (
{
id: '1',
actionTypeId: 'test',
description: 'My test',
+ isPreconfigured: false,
referencedByCount: 1,
config: {},
},
@@ -119,6 +120,15 @@ describe('actions_connectors_list component with items', () => {
actionTypeId: 'test2',
description: 'My test 2',
referencedByCount: 1,
+ isPreconfigured: false,
+ config: {},
+ },
+ {
+ id: '3',
+ actionTypeId: 'test2',
+ description: 'My preconfigured test 2',
+ referencedByCount: 1,
+ isPreconfigured: true,
config: {},
},
]);
@@ -185,7 +195,11 @@ describe('actions_connectors_list component with items', () => {
it('renders table of connectors', () => {
expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1);
- expect(wrapper.find('EuiTableRow')).toHaveLength(2);
+ expect(wrapper.find('EuiTableRow')).toHaveLength(3);
+ });
+
+ it('renders table with preconfigured connectors', () => {
+ expect(wrapper.find('[data-test-subj="preConfiguredTitleMessage"]')).toHaveLength(2);
});
test('if select item for edit should render ConnectorEditFlyout', () => {
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
index 47e058f4739463..043a644489d82c 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
@@ -15,6 +15,9 @@ import {
EuiIconTip,
EuiFlexGroup,
EuiFlexItem,
+ EuiBetaBadge,
+ EuiToolTip,
+ EuiButtonIcon,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -200,31 +203,58 @@ export const ActionsConnectorsList: React.FunctionComponent = () => {
},
},
{
- field: '',
+ field: 'isPreconfigured',
name: '',
- actions: [
- {
- enabled: () => canDelete,
- 'data-test-subj': 'deleteConnector',
- name: i18n.translate(
- 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionName',
- { defaultMessage: 'Delete' }
- ),
- description: canDelete
- ? i18n.translate(
- 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDescription',
- { defaultMessage: 'Delete this connector' }
- )
- : i18n.translate(
- 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDisabledDescription',
- { defaultMessage: 'Unable to delete connectors' }
- ),
- type: 'icon',
- icon: 'trash',
- color: 'danger',
- onClick: (item: ActionConnectorTableItem) => setConnectorsToDelete([item.id]),
- },
- ],
+ render: (value: number, item: ActionConnectorTableItem) => {
+ if (item.isPreconfigured) {
+ return (
+
+
+
+
+
+ );
+ }
+ return (
+
+
+
+ setConnectorsToDelete([item.id])}
+ iconType={'trash'}
+ />
+
+
+
+ );
+ },
},
];
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts
index 029af1ea06e4fb..c94e7116c5cea6 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts
@@ -65,23 +65,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// need this two out of popup clicks to close them
await nameInput.click();
+ // test for normal connector
+ await testSubjects.click('.webhook-ActionTypeSelectOption');
+ const webhookBodyInput = await find.byCssSelector('.ace_text-input');
+ await webhookBodyInput.focus();
+ await webhookBodyInput.type('{\\"test\\":1}');
+
+ await testSubjects.click('addAlertActionButton');
+ // pre-configured connector is loaded an displayed correctly
await testSubjects.click('.slack-ActionTypeSelectOption');
- await testSubjects.click('createActionConnectorButton');
- const connectorNameInput = await testSubjects.find('nameInput');
- await connectorNameInput.click();
- await connectorNameInput.clearValue();
- const connectorName = generateUniqueKey();
- await connectorNameInput.type(connectorName);
- const slackWebhookUrlInput = await testSubjects.find('slackWebhookUrlInput');
- await slackWebhookUrlInput.click();
- await slackWebhookUrlInput.clearValue();
- await slackWebhookUrlInput.type('https://test');
- await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)');
+ expect(await (await find.byCssSelector('#my-slack1')).isDisplayed()).to.be(true);
const loggingMessageInput = await testSubjects.find('slackMessageTextArea');
await loggingMessageInput.click();
await loggingMessageInput.clearValue();
await loggingMessageInput.type('test message');
- await testSubjects.click('slackAddVariableButton');
+ await testSubjects.click('messageAddVariableButton');
const variableMenuButton = await testSubjects.find('variableMenuButton-0');
await variableMenuButton.click();
await testSubjects.click('saveAlertButton');
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
index b5bcd33c3b9ab1..0e6f991be24d0c 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
@@ -184,5 +184,30 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getConnectorsList();
expect(searchResultsAfterDelete.length).to.eql(0);
});
+
+ it('should not be able to delete a pre-configured connector', async () => {
+ const preconfiguredConnectorName = 'xyz';
+ await pageObjects.triggersActionsUI.searchConnectors(preconfiguredConnectorName);
+
+ const searchResults = await pageObjects.triggersActionsUI.getConnectorsList();
+ expect(searchResults.length).to.eql(1);
+
+ expect(await testSubjects.exists('deleteConnector')).to.be(false);
+ expect(await testSubjects.exists('preConfiguredTitleMessage')).to.be(true);
+ });
+
+ it('should not be able to edit a pre-configured connector', async () => {
+ const preconfiguredConnectorName = 'xyz';
+
+ await pageObjects.triggersActionsUI.searchConnectors(preconfiguredConnectorName);
+
+ const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
+ expect(searchResultsBeforeEdit.length).to.eql(1);
+
+ await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
+
+ expect(await testSubjects.exists('preconfiguredBadge')).to.be(true);
+ expect(await testSubjects.exists('saveEditedActionButton')).to.be(false);
+ });
});
};
diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
index 2a0358160da512..3e5a8c57c4c7e6 100644
--- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
@@ -33,7 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// put the fetch code in a retry block with a timeout.
let alert: any;
await retry.tryForTime(15000, async () => {
- const apiResponse = await supertest.get('/api/alert/_find');
+ const apiResponse = await supertest.get('/api/alert/_find?search=uptime-test');
const alertsFromThisTest = apiResponse.body.data.filter(
({ name }: { name: string }) => name === 'uptime-test'
);
@@ -54,25 +54,27 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
tags,
} = alert;
- // we're not testing the flyout's ability to associate alerts with action connectors
- expect(actions).to.eql([]);
+ try {
+ // we're not testing the flyout's ability to associate alerts with action connectors
+ expect(actions).to.eql([]);
- expect(alertTypeId).to.eql('xpack.uptime.alerts.monitorStatus');
- expect(consumer).to.eql('uptime');
- expect(interval).to.eql('11m');
- expect(tags).to.eql(['uptime', 'another']);
- expect(numTimes).to.be(3);
- expect(timerange.from).to.be('now-1h');
- expect(timerange.to).to.be('now');
- expect(locations).to.eql(['mpls']);
- expect(filters).to.eql(
- '{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],"minimum_should_match":1}}'
- );
-
- await supertest
- .delete(`/api/alert/${id}`)
- .set('kbn-xsrf', 'true')
- .expect(204);
+ expect(alertTypeId).to.eql('xpack.uptime.alerts.monitorStatus');
+ expect(consumer).to.eql('uptime');
+ expect(interval).to.eql('11m');
+ expect(tags).to.eql(['uptime', 'another']);
+ expect(numTimes).to.be(3);
+ expect(timerange.from).to.be('now-1h');
+ expect(timerange.to).to.be('now');
+ expect(locations).to.eql(['mpls']);
+ expect(filters).to.eql(
+ '{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],"minimum_should_match":1}}'
+ );
+ } finally {
+ await supertest
+ .delete(`/api/alert/${id}`)
+ .set('kbn-xsrf', 'true')
+ .expect(204);
+ }
});
});
};
diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts
index 538817bd9d14c9..a620b1d9533767 100644
--- a/x-pack/test/functional_with_es_ssl/config.ts
+++ b/x-pack/test/functional_with_es_ssl/config.ts
@@ -52,6 +52,16 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
`--plugin-path=${join(__dirname, 'fixtures', 'plugins', 'alerts')}`,
'--xpack.actions.enabled=true',
'--xpack.alerting.enabled=true',
+ `--xpack.actions.preconfigured=${JSON.stringify([
+ {
+ id: 'my-slack1',
+ actionTypeId: '.slack',
+ name: 'Slack#xyz',
+ config: {
+ webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz',
+ },
+ },
+ ])}`,
],
},
};
diff --git a/yarn.lock b/yarn.lock
index 11abd95498c8d2..8ca25cc18a8a29 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1401,10 +1401,10 @@
resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.4.0.tgz#883197b7f4bf3c2dd994f53b274769ddfa2bf79a"
integrity sha512-uGBKGCNghTgUZPHClji/00v+AKt5nidPTGOIbcT+lbTPVxNB6QPpPLGWtXyrg3QZAxobPM/LAZB1mAqtJeq44Q==
-"@elastic/request-crypto@1.1.2":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@elastic/request-crypto/-/request-crypto-1.1.2.tgz#2e323550f546f6286994126d462a9ea480a3bfb1"
- integrity sha512-i73wjj1Qi8dGJIy170Z8xyJ760mFNjTbdmcp/nEczqWD0miNW6I5wZ5MNrv7M6CXn2m1wMXiT6qzDYd93Hv1Dw==
+"@elastic/request-crypto@1.1.4":
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/@elastic/request-crypto/-/request-crypto-1.1.4.tgz#2189d5fea65f7afe1de9f5fa3d0dd420e93e3124"
+ integrity sha512-D5CzSGKkM6BdrVB/HRRTheMsNQOcd2FMUup0O/1hIGUBE8zHh2AYbmSNSpD6LyQAgY39mGkARUi/x+SO0ccVvg==
dependencies:
"@elastic/node-crypto" "1.1.1"
"@types/node-jose" "1.1.0"
@@ -5061,6 +5061,13 @@
dependencies:
"@types/yargs-parser" "*"
+"@types/yauzl@^2.9.1":
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
+ integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==
+ dependencies:
+ "@types/node" "*"
+
"@types/zen-observable@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
@@ -8723,16 +8730,16 @@ chrome-trace-event@^1.0.2:
dependencies:
tslib "^1.9.0"
-chromedriver@^80.0.1:
- version "80.0.1"
- resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-80.0.1.tgz#35c1642e2d864b9e8262f291003e455b0e422917"
- integrity sha512-VfRtZUpBUIjeypS+xM40+VD9g4Drv7L2VibG/4+0zX3mMx4KayN6gfKETycPfO6JwQXTLSxEr58fRcrsa8r5xQ==
+chromedriver@^81.0.0:
+ version "81.0.0"
+ resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-81.0.0.tgz#690ba333aedf2b4c4933b6590c3242d3e5f28f3c"
+ integrity sha512-BA++IQ7O1FzHmNpzMlOfLiSBvPZ946uuhtJjZHEIr/Gb+Ha9jiuGbHiT45l6O3XGbQ8BAwvbmdisjl4rTxro4A==
dependencies:
"@testim/chrome-version" "^1.0.7"
axios "^0.19.2"
del "^5.1.0"
- extract-zip "^1.6.7"
- mkdirp "^1.0.3"
+ extract-zip "^2.0.0"
+ mkdirp "^1.0.4"
tcp-port-used "^1.0.1"
ci-info@^1.0.0:
@@ -13161,6 +13168,17 @@ extract-zip@^1.6.6, extract-zip@^1.6.7, extract-zip@^1.7.0:
mkdirp "^0.5.4"
yauzl "^2.10.0"
+extract-zip@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492"
+ integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==
+ dependencies:
+ debug "^4.1.1"
+ get-stream "^5.1.0"
+ yauzl "^2.10.0"
+ optionalDependencies:
+ "@types/yauzl" "^2.9.1"
+
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -20622,10 +20640,10 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkd
dependencies:
minimist "^1.2.5"
-mkdirp@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea"
- integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==
+mkdirp@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+ integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mocha-junit-reporter@^1.23.1:
version "1.23.1"