Skip to content

Commit fba5128

Browse files
[Ingest] Edit datasource UI (#64727)
* Adjust NewDatasource type to exclude stream `agent_stream` property, add additional datasource hooks * Initial pass at edit datasource UI * Clean up dupe code, fix submit button not enabled after re-selecting a package * Remove delete config functionality from list page * Show validation errors for data source name and description fields * Fix types * Add success toasts * Review fixes, clean up i18n Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 9b65cbd commit fba5128

File tree

18 files changed

+462
-198
lines changed

18 files changed

+462
-198
lines changed

x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,19 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
import { Datasource, NewDatasource, DatasourceInput } from '../types';
6+
import { Datasource, DatasourceInput } from '../types';
77
import { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource';
88

99
describe('Ingest Manager - storedDatasourceToAgentDatasource', () => {
10-
const mockNewDatasource: NewDatasource = {
10+
const mockDatasource: Datasource = {
11+
id: 'some-uuid',
1112
name: 'mock-datasource',
1213
description: '',
1314
config_id: '',
1415
enabled: true,
1516
output_id: '',
1617
namespace: 'default',
1718
inputs: [],
18-
};
19-
20-
const mockDatasource: Datasource = {
21-
...mockNewDatasource,
22-
id: 'some-uuid',
2319
revision: 1,
2420
};
2521

@@ -107,17 +103,6 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => {
107103
});
108104
});
109105

110-
it('uses name for id when id is not provided in case of new datasource', () => {
111-
expect(storedDatasourceToAgentDatasource(mockNewDatasource)).toEqual({
112-
id: 'mock-datasource',
113-
name: 'mock-datasource',
114-
namespace: 'default',
115-
enabled: true,
116-
use_output: 'default',
117-
inputs: [],
118-
});
119-
});
120-
121106
it('returns agent datasource config with flattened input and package stream', () => {
122107
expect(storedDatasourceToAgentDatasource({ ...mockDatasource, inputs: [mockInput] })).toEqual({
123108
id: 'some-uuid',

x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
import { Datasource, NewDatasource, FullAgentConfigDatasource } from '../types';
6+
import { Datasource, FullAgentConfigDatasource } from '../types';
77
import { DEFAULT_OUTPUT } from '../constants';
88

99
export const storedDatasourceToAgentDatasource = (
10-
datasource: Datasource | NewDatasource
10+
datasource: Datasource
1111
): FullAgentConfigDatasource => {
12-
const { name, namespace, enabled, package: pkg, inputs } = datasource;
12+
const { id, name, namespace, enabled, package: pkg, inputs } = datasource;
1313

1414
const fullDatasource: FullAgentConfigDatasource = {
15-
id: 'id' in datasource ? datasource.id : name,
15+
id: id || name,
1616
name,
1717
namespace,
1818
enabled,

x-pack/plugins/ingest_manager/common/types/models/agent_config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
7-
import { SavedObjectAttributes } from 'src/core/public';
86
import {
97
Datasource,
108
DatasourcePackage,
@@ -26,7 +24,7 @@ export interface NewAgentConfig {
2624
monitoring_enabled?: Array<'logs' | 'metrics'>;
2725
}
2826

29-
export interface AgentConfig extends NewAgentConfig, SavedObjectAttributes {
27+
export interface AgentConfig extends NewAgentConfig {
3028
id: string;
3129
status: AgentConfigStatus;
3230
datasources: string[] | Datasource[];

x-pack/plugins/ingest_manager/common/types/models/datasource.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,29 @@ export interface DatasourceConfigRecordEntry {
1717

1818
export type DatasourceConfigRecord = Record<string, DatasourceConfigRecordEntry>;
1919

20-
export interface DatasourceInputStream {
20+
export interface NewDatasourceInputStream {
2121
id: string;
2222
enabled: boolean;
2323
dataset: string;
2424
processors?: string[];
2525
config?: DatasourceConfigRecord;
2626
vars?: DatasourceConfigRecord;
27+
}
28+
29+
export interface DatasourceInputStream extends NewDatasourceInputStream {
2730
agent_stream?: any;
2831
}
2932

30-
export interface DatasourceInput {
33+
export interface NewDatasourceInput {
3134
type: string;
3235
enabled: boolean;
3336
processors?: string[];
3437
config?: DatasourceConfigRecord;
3538
vars?: DatasourceConfigRecord;
39+
streams: NewDatasourceInputStream[];
40+
}
41+
42+
export interface DatasourceInput extends Omit<NewDatasourceInput, 'streams'> {
3643
streams: DatasourceInputStream[];
3744
}
3845

@@ -44,10 +51,11 @@ export interface NewDatasource {
4451
enabled: boolean;
4552
package?: DatasourcePackage;
4653
output_id: string;
47-
inputs: DatasourceInput[];
54+
inputs: NewDatasourceInput[];
4855
}
4956

50-
export type Datasource = NewDatasource & {
57+
export interface Datasource extends Omit<NewDatasource, 'inputs'> {
5158
id: string;
59+
inputs: DatasourceInput[];
5260
revision: number;
53-
};
61+
}

x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/datasource.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
*/
66
import { sendRequest, useRequest } from './use_request';
77
import { datasourceRouteService } from '../../services';
8-
import { CreateDatasourceRequest, CreateDatasourceResponse } from '../../types';
8+
import {
9+
CreateDatasourceRequest,
10+
CreateDatasourceResponse,
11+
UpdateDatasourceRequest,
12+
UpdateDatasourceResponse,
13+
} from '../../types';
914
import {
1015
DeleteDatasourcesRequest,
1116
DeleteDatasourcesResponse,
1217
GetDatasourcesRequest,
1318
GetDatasourcesResponse,
19+
GetOneDatasourceResponse,
1420
} from '../../../../../common/types/rest_spec';
1521

1622
export const sendCreateDatasource = (body: CreateDatasourceRequest['body']) => {
@@ -21,6 +27,17 @@ export const sendCreateDatasource = (body: CreateDatasourceRequest['body']) => {
2127
});
2228
};
2329

30+
export const sendUpdateDatasource = (
31+
datasourceId: string,
32+
body: UpdateDatasourceRequest['body']
33+
) => {
34+
return sendRequest<UpdateDatasourceResponse>({
35+
path: datasourceRouteService.getUpdatePath(datasourceId),
36+
method: 'put',
37+
body: JSON.stringify(body),
38+
});
39+
};
40+
2441
export const sendDeleteDatasource = (body: DeleteDatasourcesRequest['body']) => {
2542
return sendRequest<DeleteDatasourcesResponse>({
2643
path: datasourceRouteService.getDeletePath(),
@@ -36,3 +53,10 @@ export function useGetDatasources(query: GetDatasourcesRequest['query']) {
3653
query,
3754
});
3855
}
56+
57+
export const sendGetOneDatasource = (datasourceId: string) => {
58+
return sendRequest<GetOneDatasourceResponse>({
59+
path: datasourceRouteService.getInfoPath(datasourceId),
60+
method: 'get',
61+
});
62+
};

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,29 @@ export const CreateDatasourcePageLayout: React.FunctionComponent<{
3939
<EuiFlexItem>
4040
<EuiText>
4141
<h1>
42-
<FormattedMessage
43-
id="xpack.ingestManager.createDatasource.pageTitle"
44-
defaultMessage="Add data source"
45-
/>
42+
{from === 'edit' ? (
43+
<FormattedMessage
44+
id="xpack.ingestManager.editDatasource.pageTitle"
45+
defaultMessage="Edit data source"
46+
/>
47+
) : (
48+
<FormattedMessage
49+
id="xpack.ingestManager.createDatasource.pageTitle"
50+
defaultMessage="Add data source"
51+
/>
52+
)}
4653
</h1>
4754
</EuiText>
4855
</EuiFlexItem>
4956
<EuiFlexItem>
5057
<EuiSpacer size="s" />
5158
<EuiText color="subdued" size="s">
52-
{from === 'config' ? (
59+
{from === 'edit' ? (
60+
<FormattedMessage
61+
id="xpack.ingestManager.editDatasource.pageDescription"
62+
defaultMessage="Follow the instructions below to edit this data source."
63+
/>
64+
) : from === 'config' ? (
5365
<FormattedMessage
5466
id="xpack.ingestManager.createDatasource.pageDescriptionfromConfig"
5567
defaultMessage="Follow the instructions below to add an integration to this agent configuration."
@@ -68,7 +80,7 @@ export const CreateDatasourcePageLayout: React.FunctionComponent<{
6880
<EuiFlexGroup justifyContent="flexEnd" direction={'row'} gutterSize="xl">
6981
<EuiFlexItem grow={false}>
7082
<EuiSpacer size="s" />
71-
{agentConfig && from === 'config' ? (
83+
{agentConfig && (from === 'config' || from === 'edit') ? (
7284
<EuiDescriptionList style={{ textAlign: 'right' }} textStyle="reverse">
7385
<EuiDescriptionListTitle>
7486
<FormattedMessage

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
} from '../../../hooks';
2929
import { useLinks as useEPMLinks } from '../../epm/hooks';
3030
import { CreateDatasourcePageLayout, ConfirmCreateDatasourceModal } from './components';
31-
import { CreateDatasourceFrom } from './types';
31+
import { CreateDatasourceFrom, DatasourceFormState } from './types';
3232
import { DatasourceValidationResults, validateDatasource, validationHasErrors } from './services';
3333
import { StepSelectPackage } from './step_select_package';
3434
import { StepSelectConfig } from './step_select_config';
@@ -85,6 +85,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
8585
const updatePackageInfo = (updatedPackageInfo: PackageInfo | undefined) => {
8686
if (updatedPackageInfo) {
8787
setPackageInfo(updatedPackageInfo);
88+
setFormState('VALID');
8889
} else {
8990
setFormState('INVALID');
9091
setPackageInfo(undefined);
@@ -152,9 +153,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
152153
const cancelUrl = from === 'config' ? CONFIG_URL : PACKAGE_URL;
153154

154155
// Save datasource
155-
const [formState, setFormState] = useState<
156-
'VALID' | 'INVALID' | 'CONFIRM' | 'LOADING' | 'SUBMITTED'
157-
>('INVALID');
156+
const [formState, setFormState] = useState<DatasourceFormState>('INVALID');
158157
const saveDatasource = async () => {
159158
setFormState('LOADING');
160159
const result = await sendCreateDatasource(datasource);
@@ -174,6 +173,23 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
174173
const { error } = await saveDatasource();
175174
if (!error) {
176175
history.push(`${AGENT_CONFIG_DETAILS_PATH}${agentConfig ? agentConfig.id : configId}`);
176+
notifications.toasts.addSuccess({
177+
title: i18n.translate('xpack.ingestManager.createDatasource.addedNotificationTitle', {
178+
defaultMessage: `Successfully added '{datasourceName}'`,
179+
values: {
180+
datasourceName: datasource.name,
181+
},
182+
}),
183+
text:
184+
agentCount && agentConfig
185+
? i18n.translate('xpack.ingestManager.createDatasource.addedNotificationMessage', {
186+
defaultMessage: `Fleet will deploy updates to all agents that use the '{agentConfigName}' configuration`,
187+
values: {
188+
agentConfigName: agentConfig.name,
189+
},
190+
})
191+
: undefined,
192+
});
177193
} else {
178194
notifications.toasts.addError(error, {
179195
title: 'Error',
@@ -229,6 +245,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
229245
packageInfo={packageInfo}
230246
datasource={datasource}
231247
updateDatasource={updateDatasource}
248+
validationResults={validationResults!}
232249
/>
233250
) : null,
234251
},
@@ -240,7 +257,6 @@ export const CreateDatasourcePage: React.FunctionComponent = () => {
240257
children:
241258
agentConfig && packageInfo ? (
242259
<StepConfigureDatasource
243-
agentConfig={agentConfig}
244260
packageInfo={packageInfo}
245261
datasource={datasource}
246262
updateDatasource={updateDatasource}

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
import React, { useEffect } from 'react';
6+
import React from 'react';
77
import { FormattedMessage } from '@kbn/i18n/react';
88
import {
99
EuiPanel,
@@ -15,75 +15,21 @@ import {
1515
EuiCallOut,
1616
} from '@elastic/eui';
1717
import { i18n } from '@kbn/i18n';
18-
import {
19-
AgentConfig,
20-
PackageInfo,
21-
Datasource,
22-
NewDatasource,
23-
DatasourceInput,
24-
} from '../../../types';
18+
import { PackageInfo, NewDatasource, DatasourceInput } from '../../../types';
2519
import { Loading } from '../../../components';
26-
import { packageToConfigDatasourceInputs } from '../../../services';
2720
import { DatasourceValidationResults, validationHasErrors } from './services';
2821
import { DatasourceInputPanel } from './components';
2922

3023
export const StepConfigureDatasource: React.FunctionComponent<{
31-
agentConfig: AgentConfig;
3224
packageInfo: PackageInfo;
3325
datasource: NewDatasource;
3426
updateDatasource: (fields: Partial<NewDatasource>) => void;
3527
validationResults: DatasourceValidationResults;
3628
submitAttempted: boolean;
37-
}> = ({
38-
agentConfig,
39-
packageInfo,
40-
datasource,
41-
updateDatasource,
42-
validationResults,
43-
submitAttempted,
44-
}) => {
45-
// Form show/hide states
46-
29+
}> = ({ packageInfo, datasource, updateDatasource, validationResults, submitAttempted }) => {
4730
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
4831

49-
// Update datasource's package and config info
50-
useEffect(() => {
51-
const dsPackage = datasource.package;
52-
const currentPkgKey = dsPackage ? `${dsPackage.name}-${dsPackage.version}` : '';
53-
const pkgKey = `${packageInfo.name}-${packageInfo.version}`;
54-
55-
// If package has changed, create shell datasource with input&stream values based on package info
56-
if (currentPkgKey !== pkgKey) {
57-
// Existing datasources on the agent config using the package name, retrieve highest number appended to datasource name
58-
const dsPackageNamePattern = new RegExp(`${packageInfo.name}-(\\d+)`);
59-
const dsWithMatchingNames = (agentConfig.datasources as Datasource[])
60-
.filter(ds => Boolean(ds.name.match(dsPackageNamePattern)))
61-
.map(ds => parseInt(ds.name.match(dsPackageNamePattern)![1], 10))
62-
.sort();
63-
64-
updateDatasource({
65-
name: `${packageInfo.name}-${
66-
dsWithMatchingNames.length ? dsWithMatchingNames[dsWithMatchingNames.length - 1] + 1 : 1
67-
}`,
68-
package: {
69-
name: packageInfo.name,
70-
title: packageInfo.title,
71-
version: packageInfo.version,
72-
},
73-
inputs: packageToConfigDatasourceInputs(packageInfo),
74-
});
75-
}
76-
77-
// If agent config has changed, update datasource's config ID and namespace
78-
if (datasource.config_id !== agentConfig.id) {
79-
updateDatasource({
80-
config_id: agentConfig.id,
81-
namespace: agentConfig.namespace,
82-
});
83-
}
84-
}, [datasource.package, datasource.config_id, agentConfig, packageInfo, updateDatasource]);
85-
86-
// Step B, configure inputs (and their streams)
32+
// Configure inputs (and their streams)
8733
// Assume packages only export one datasource for now
8834
const renderConfigureInputs = () =>
8935
packageInfo.datasources &&

0 commit comments

Comments
 (0)