mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* saved objects: allow partial update without references For normal attributes, the update API for saved objects supports partial updates, where it will only attempt to change those attributes you specify. References should behave the same way otherwise they will be replaced entirely if you call update without specifying the original references.
This commit is contained in:
parent
dabeeafa7b
commit
3f56b9b265
12 changed files with 150 additions and 25 deletions
|
@ -8,7 +8,7 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectsUpdateResponse<T extends SavedObjectAttributes = any> extends Omit<SavedObject<T>, 'attributes'>
|
||||
export interface SavedObjectsUpdateResponse<T extends SavedObjectAttributes = any> extends Omit<SavedObject<T>, 'attributes' | 'references'>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
@ -16,4 +16,5 @@ export interface SavedObjectsUpdateResponse<T extends SavedObjectAttributes = an
|
|||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [attributes](./kibana-plugin-server.savedobjectsupdateresponse.attributes.md) | <code>Partial<T></code> | |
|
||||
| [references](./kibana-plugin-server.savedobjectsupdateresponse.references.md) | <code>SavedObjectReference[] | undefined</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) > [references](./kibana-plugin-server.savedobjectsupdateresponse.references.md)
|
||||
|
||||
## SavedObjectsUpdateResponse.references property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
references: SavedObjectReference[] | undefined;
|
||||
```
|
|
@ -1744,6 +1744,68 @@ describe('SavedObjectsRepository', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('does not pass references if omitted', async () => {
|
||||
await savedObjectsRepository.update(
|
||||
type,
|
||||
id,
|
||||
{ title: 'Testing' }
|
||||
);
|
||||
|
||||
expect(callAdminCluster).toHaveBeenCalledTimes(1);
|
||||
expect(callAdminCluster).not.toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
body: {
|
||||
doc: expect.objectContaining({
|
||||
references: [],
|
||||
})
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('passes references if they are provided', async () => {
|
||||
await savedObjectsRepository.update(
|
||||
type,
|
||||
id,
|
||||
{ title: 'Testing' },
|
||||
{ references: ['foo'] }
|
||||
);
|
||||
|
||||
expect(callAdminCluster).toHaveBeenCalledTimes(1);
|
||||
expect(callAdminCluster).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
body: {
|
||||
doc: expect.objectContaining({
|
||||
references: ['foo'],
|
||||
})
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('passes empty references array if empty references array is provided', async () => {
|
||||
await savedObjectsRepository.update(
|
||||
type,
|
||||
id,
|
||||
{ title: 'Testing' },
|
||||
{ references: [] }
|
||||
);
|
||||
|
||||
expect(callAdminCluster).toHaveBeenCalledTimes(1);
|
||||
expect(callAdminCluster).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
body: {
|
||||
doc: expect.objectContaining({
|
||||
references: [],
|
||||
})
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it(`prepends namespace to the id but doesn't add namespace to body when providing namespace for namespaced type`, async () => {
|
||||
await savedObjectsRepository.update(
|
||||
'index-pattern',
|
||||
|
|
|
@ -644,9 +644,19 @@ export class SavedObjectsRepository {
|
|||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
}
|
||||
|
||||
const { version, namespace, references = [] } = options;
|
||||
const { version, namespace, references } = options;
|
||||
|
||||
const time = this._getCurrentTime();
|
||||
|
||||
const doc = {
|
||||
[type]: attributes,
|
||||
updated_at: time,
|
||||
references,
|
||||
};
|
||||
if (!Array.isArray(doc.references)) {
|
||||
delete doc.references;
|
||||
}
|
||||
|
||||
const response = await this._writeToCluster('update', {
|
||||
id: this._serializer.generateRawId(namespace, type, id),
|
||||
index: this.getIndexForType(type),
|
||||
|
@ -654,11 +664,7 @@ export class SavedObjectsRepository {
|
|||
refresh: 'wait_for',
|
||||
ignore: [404],
|
||||
body: {
|
||||
doc: {
|
||||
[type]: attributes,
|
||||
updated_at: time,
|
||||
references,
|
||||
},
|
||||
doc,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -114,8 +114,9 @@ export interface SavedObjectsBulkResponse<T extends SavedObjectAttributes = any>
|
|||
* @public
|
||||
*/
|
||||
export interface SavedObjectsUpdateResponse<T extends SavedObjectAttributes = any>
|
||||
extends Omit<SavedObject<T>, 'attributes'> {
|
||||
extends Omit<SavedObject<T>, 'attributes' | 'references'> {
|
||||
attributes: Partial<T>;
|
||||
references: SavedObjectReference[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1511,9 +1511,11 @@ export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions {
|
|||
// Warning: (ae-forgotten-export) The symbol "Omit" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsUpdateResponse<T extends SavedObjectAttributes = any> extends Omit<SavedObject<T>, 'attributes'> {
|
||||
export interface SavedObjectsUpdateResponse<T extends SavedObjectAttributes = any> extends Omit<SavedObject<T>, 'attributes' | 'references'> {
|
||||
// (undocumented)
|
||||
attributes: Partial<T>;
|
||||
// (undocumented)
|
||||
references: SavedObjectReference[] | undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
|
|
@ -99,7 +99,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => {
|
|||
'index-pattern',
|
||||
'logstash-*',
|
||||
{ title: 'Testing' },
|
||||
{ version: 'foo', references: [] }
|
||||
{ version: 'foo' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,15 +53,13 @@ export const createUpdateRoute = (prereqs: Prerequisites) => {
|
|||
payload: Joi.object({
|
||||
attributes: Joi.object().required(),
|
||||
version: Joi.string(),
|
||||
references: Joi.array()
|
||||
.items(
|
||||
Joi.object().keys({
|
||||
name: Joi.string().required(),
|
||||
type: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
)
|
||||
.default([]),
|
||||
references: Joi.array().items(
|
||||
Joi.object().keys({
|
||||
name: Joi.string().required(),
|
||||
type: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
),
|
||||
}).required(),
|
||||
},
|
||||
handler(request: UpdateRequest) {
|
||||
|
|
|
@ -52,11 +52,55 @@ export default function ({ getService }) {
|
|||
attributes: {
|
||||
title: 'My second favorite vis'
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not pass references if omitted', async () => {
|
||||
const resp = await supertest
|
||||
.put(`/api/saved_objects/visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab`)
|
||||
.send({
|
||||
attributes: {
|
||||
title: 'foo'
|
||||
}
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(resp.body).not.to.have.property('references');
|
||||
});
|
||||
|
||||
it('passes references if they are provided', async () => {
|
||||
const references = [{ id: 'foo', name: 'Foo', type: 'visualization' }];
|
||||
|
||||
const resp = await supertest
|
||||
.put(`/api/saved_objects/visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab`)
|
||||
.send({
|
||||
attributes: {
|
||||
title: 'foo'
|
||||
},
|
||||
references
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(resp.body).to.have.property('references');
|
||||
expect(resp.body.references).to.eql(references);
|
||||
});
|
||||
|
||||
it('passes empty references array if empty references array is provided', async () => {
|
||||
const resp = await supertest
|
||||
.put(`/api/saved_objects/visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab`)
|
||||
.send({
|
||||
attributes: {
|
||||
title: 'foo'
|
||||
},
|
||||
references: []
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(resp.body).to.have.property('references');
|
||||
expect(resp.body.references).to.eql([]);
|
||||
});
|
||||
|
||||
describe('unknown id', () => {
|
||||
it('should return a generic 404', async () => {
|
||||
await supertest
|
||||
|
|
|
@ -448,7 +448,7 @@ export class AlertsClient {
|
|||
private getAlertFromRaw(
|
||||
id: string,
|
||||
rawAlert: Partial<RawAlert>,
|
||||
references: SavedObjectReference[]
|
||||
references: SavedObjectReference[] | undefined
|
||||
) {
|
||||
if (!rawAlert.actions) {
|
||||
return {
|
||||
|
@ -456,7 +456,7 @@ export class AlertsClient {
|
|||
...rawAlert,
|
||||
};
|
||||
}
|
||||
const actions = this.injectReferencesIntoActions(rawAlert.actions, references);
|
||||
const actions = this.injectReferencesIntoActions(rawAlert.actions, references || []);
|
||||
return {
|
||||
id,
|
||||
...rawAlert,
|
||||
|
|
|
@ -481,7 +481,9 @@ function taskInstanceToAttributes(doc: TaskInstance): SavedObjectAttributes {
|
|||
};
|
||||
}
|
||||
|
||||
function savedObjectToConcreteTaskInstance(savedObject: SavedObject): ConcreteTaskInstance {
|
||||
function savedObjectToConcreteTaskInstance(
|
||||
savedObject: Omit<SavedObject, 'references'>
|
||||
): ConcreteTaskInstance {
|
||||
return {
|
||||
...savedObject.attributes,
|
||||
id: savedObject.id,
|
||||
|
|
|
@ -87,7 +87,6 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
|
|||
attributes: {
|
||||
name: 'My second favorite',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -112,7 +111,6 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
|
|||
attributes: {
|
||||
title: 'My second favorite vis',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue