Skip to content

Commit ceb20a5

Browse files
committed
Disable checking for conflicts when copying saved objects (#83575)
1 parent 896187e commit ceb20a5

72 files changed

Lines changed: 1769 additions & 191 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/api/spaces-management/copy_saved_objects.asciidoc

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,17 @@ You can request to overwrite any objects that already exist in the target space
5151
(Optional, boolean) When set to `true`, all saved objects related to the specified saved objects will also be copied into the target
5252
spaces. The default value is `false`.
5353

54+
`createNewCopies`::
55+
(Optional, boolean) Creates new copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict
56+
errors are avoided. The default value is `true`.
57+
+
58+
NOTE: This cannot be used with the `overwrite` option.
59+
5460
`overwrite`::
5561
(Optional, boolean) When set to `true`, all conflicts are automatically overidden. When a saved object with a matching `type` and `id`
5662
exists in the target space, that version is replaced with the version from the source space. The default value is `false`.
63+
+
64+
NOTE: This cannot be used with the `createNewCopies` option.
5765

5866
[role="child_attributes"]
5967
[[spaces-api-copy-saved-objects-response-body]]
@@ -128,8 +136,7 @@ $ curl -X POST api/spaces/_copy_saved_objects
128136
"id": "my-dashboard"
129137
}],
130138
"spaces": ["marketing"],
131-
"includeReferences": true,
132-
"createNewcopies": true
139+
"includeReferences": true
133140
}
134141
----
135142
// KIBANA
@@ -193,7 +200,8 @@ $ curl -X POST api/spaces/_copy_saved_objects
193200
"id": "my-dashboard"
194201
}],
195202
"spaces": ["marketing"],
196-
"includeReferences": true
203+
"includeReferences": true,
204+
"createNewCopies": false
197205
}
198206
----
199207
// KIBANA
@@ -254,7 +262,8 @@ $ curl -X POST api/spaces/_copy_saved_objects
254262
"id": "my-dashboard"
255263
}],
256264
"spaces": ["marketing", "sales"],
257-
"includeReferences": true
265+
"includeReferences": true,
266+
"createNewCopies": false
258267
}
259268
----
260269
// KIBANA
@@ -405,7 +414,8 @@ $ curl -X POST api/spaces/_copy_saved_objects
405414
"id": "my-dashboard"
406415
}],
407416
"spaces": ["marketing"],
408-
"includeReferences": true
417+
"includeReferences": true,
418+
"createNewCopies": false
409419
}
410420
----
411421
// KIBANA

docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ Execute the <<spaces-api-copy-saved-objects,copy saved objects to space API>>, w
4545
`includeReferences`::
4646
(Optional, boolean) When set to `true`, all saved objects related to the specified saved objects are copied into the target spaces. The `includeReferences` must be the same values used during the failed <<spaces-api-copy-saved-objects, copy saved objects to space API>> operation. The default value is `false`.
4747

48+
`createNewCopies`::
49+
(Optional, boolean) Creates new copies of the saved objects, regenerates each object ID, and resets the origin. When enabled during the
50+
initial copy, also enable when resolving copy errors. The default value is `true`.
51+
4852
`retries`::
4953
(Required, object) The retry operations to attempt, which can specify how to resolve different types of errors. Object keys represent the
5054
target space IDs.
@@ -148,6 +152,7 @@ $ curl -X POST api/spaces/_resolve_copy_saved_objects_errors
148152
"id": "my-dashboard"
149153
}],
150154
"includeReferences": true,
155+
"createNewCopies": false,
151156
"retries": {
152157
"sales": [
153158
{
@@ -246,6 +251,7 @@ $ curl -X POST api/spaces/_resolve_copy_saved_objects_errors
246251
"id": "my-dashboard"
247252
}],
248253
"includeReferences": true,
254+
"createNewCopies": false,
249255
"retries": {
250256
"marketing": [
251257
{

docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Increments all the specified counter fields by one. Creates the document if one
99
<b>Signature:</b>
1010

1111
```typescript
12-
incrementCounter(type: string, id: string, counterFieldNames: string[], options?: SavedObjectsIncrementCounterOptions): Promise<SavedObject>;
12+
incrementCounter<T = unknown>(type: string, id: string, counterFieldNames: string[], options?: SavedObjectsIncrementCounterOptions): Promise<SavedObject<T>>;
1313
```
1414
1515
## Parameters
@@ -23,7 +23,7 @@ incrementCounter(type: string, id: string, counterFieldNames: string[], options?
2323
2424
<b>Returns:</b>
2525
26-
`Promise<SavedObject>`
26+
`Promise<SavedObject<T>>`
2727
2828
The saved object after the specified fields were incremented
2929
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
/** @internal */
21+
export const CORE_USAGE_STATS_TYPE = 'core-usage-stats';
22+
23+
/** @internal */
24+
export const CORE_USAGE_STATS_ID = 'core-usage-stats';

src/core/server/core_usage_data/core_usage_data_service.mock.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@
2020
import { PublicMethodsOf } from '@kbn/utility-types';
2121
import { BehaviorSubject } from 'rxjs';
2222
import { CoreUsageDataService } from './core_usage_data_service';
23-
import { CoreUsageData, CoreUsageDataStart } from './types';
23+
import { coreUsageStatsClientMock } from './core_usage_stats_client.mock';
24+
import { CoreUsageData, CoreUsageDataSetup, CoreUsageDataStart } from './types';
25+
26+
const createSetupContractMock = (usageStatsClient = coreUsageStatsClientMock.create()) => {
27+
const setupContract: jest.Mocked<CoreUsageDataSetup> = {
28+
registerType: jest.fn(),
29+
getClient: jest.fn().mockReturnValue(usageStatsClient),
30+
};
31+
return setupContract;
32+
};
2433

2534
const createStartContractMock = () => {
2635
const startContract: jest.Mocked<CoreUsageDataStart> = {
@@ -140,7 +149,7 @@ const createStartContractMock = () => {
140149

141150
const createMock = () => {
142151
const mocked: jest.Mocked<PublicMethodsOf<CoreUsageDataService>> = {
143-
setup: jest.fn(),
152+
setup: jest.fn().mockReturnValue(createSetupContractMock()),
144153
start: jest.fn().mockReturnValue(createStartContractMock()),
145154
stop: jest.fn(),
146155
};
@@ -149,5 +158,6 @@ const createMock = () => {
149158

150159
export const coreUsageDataServiceMock = {
151160
create: createMock,
161+
createSetupContract: createSetupContractMock,
152162
createStartContract: createStartContractMock,
153163
};

src/core/server/core_usage_data/core_usage_data_service.test.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.
3434

3535
import { CoreUsageDataService } from './core_usage_data_service';
3636
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
37+
import { typeRegistryMock } from '../saved_objects/saved_objects_type_registry.mock';
38+
import { CORE_USAGE_STATS_TYPE } from './constants';
39+
import { CoreUsageStatsClient } from './core_usage_stats_client';
3740

3841
describe('CoreUsageDataService', () => {
3942
const getTestScheduler = () =>
@@ -63,11 +66,67 @@ describe('CoreUsageDataService', () => {
6366
service = new CoreUsageDataService(coreContext);
6467
});
6568

69+
describe('setup', () => {
70+
it('creates internal repository', async () => {
71+
const metrics = metricsServiceMock.createInternalSetupContract();
72+
const savedObjectsStartPromise = Promise.resolve(
73+
savedObjectsServiceMock.createStartContract()
74+
);
75+
service.setup({ metrics, savedObjectsStartPromise });
76+
77+
const savedObjects = await savedObjectsStartPromise;
78+
expect(savedObjects.createInternalRepository).toHaveBeenCalledTimes(1);
79+
expect(savedObjects.createInternalRepository).toHaveBeenCalledWith([CORE_USAGE_STATS_TYPE]);
80+
});
81+
82+
describe('#registerType', () => {
83+
it('registers core usage stats type', async () => {
84+
const metrics = metricsServiceMock.createInternalSetupContract();
85+
const savedObjectsStartPromise = Promise.resolve(
86+
savedObjectsServiceMock.createStartContract()
87+
);
88+
const coreUsageData = service.setup({
89+
metrics,
90+
savedObjectsStartPromise,
91+
});
92+
const typeRegistry = typeRegistryMock.create();
93+
94+
coreUsageData.registerType(typeRegistry);
95+
expect(typeRegistry.registerType).toHaveBeenCalledTimes(1);
96+
expect(typeRegistry.registerType).toHaveBeenCalledWith({
97+
name: CORE_USAGE_STATS_TYPE,
98+
hidden: true,
99+
namespaceType: 'agnostic',
100+
mappings: expect.anything(),
101+
});
102+
});
103+
});
104+
105+
describe('#getClient', () => {
106+
it('returns client', async () => {
107+
const metrics = metricsServiceMock.createInternalSetupContract();
108+
const savedObjectsStartPromise = Promise.resolve(
109+
savedObjectsServiceMock.createStartContract()
110+
);
111+
const coreUsageData = service.setup({
112+
metrics,
113+
savedObjectsStartPromise,
114+
});
115+
116+
const usageStatsClient = coreUsageData.getClient();
117+
expect(usageStatsClient).toBeInstanceOf(CoreUsageStatsClient);
118+
});
119+
});
120+
});
121+
66122
describe('start', () => {
67123
describe('getCoreUsageData', () => {
68-
it('returns core metrics for default config', () => {
124+
it('returns core metrics for default config', async () => {
69125
const metrics = metricsServiceMock.createInternalSetupContract();
70-
service.setup({ metrics });
126+
const savedObjectsStartPromise = Promise.resolve(
127+
savedObjectsServiceMock.createStartContract()
128+
);
129+
service.setup({ metrics, savedObjectsStartPromise });
71130
const elasticsearch = elasticsearchServiceMock.createStart();
72131
elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({
73132
body: [
@@ -243,8 +302,11 @@ describe('CoreUsageDataService', () => {
243302
observables.push(newObservable);
244303
return newObservable as Observable<any>;
245304
});
305+
const savedObjectsStartPromise = Promise.resolve(
306+
savedObjectsServiceMock.createStartContract()
307+
);
246308

247-
service.setup({ metrics });
309+
service.setup({ metrics, savedObjectsStartPromise });
248310

249311
// Use the stopTimer$ to delay calling stop() until the third frame
250312
const stopTimer$ = cold('---a|');

src/core/server/core_usage_data/core_usage_data_service.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,29 @@ import { Subject } from 'rxjs';
2121
import { takeUntil } from 'rxjs/operators';
2222

2323
import { CoreService } from 'src/core/types';
24-
import { SavedObjectsServiceStart } from 'src/core/server';
24+
import { Logger, SavedObjectsServiceStart, SavedObjectTypeRegistry } from 'src/core/server';
2525
import { CoreContext } from '../core_context';
2626
import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config';
2727
import { HttpConfigType } from '../http';
2828
import { LoggingConfigType } from '../logging';
2929
import { SavedObjectsConfigType } from '../saved_objects/saved_objects_config';
30-
import { CoreServicesUsageData, CoreUsageData, CoreUsageDataStart } from './types';
30+
import {
31+
CoreServicesUsageData,
32+
CoreUsageData,
33+
CoreUsageDataStart,
34+
CoreUsageDataSetup,
35+
} from './types';
3136
import { isConfigured } from './is_configured';
3237
import { ElasticsearchServiceStart } from '../elasticsearch';
3338
import { KibanaConfigType } from '../kibana_config';
39+
import { coreUsageStatsType } from './core_usage_stats';
40+
import { CORE_USAGE_STATS_TYPE } from './constants';
41+
import { CoreUsageStatsClient } from './core_usage_stats_client';
3442
import { MetricsServiceSetup, OpsMetrics } from '..';
3543

3644
export interface SetupDeps {
3745
metrics: MetricsServiceSetup;
46+
savedObjectsStartPromise: Promise<SavedObjectsServiceStart>;
3847
}
3948

4049
export interface StartDeps {
@@ -60,7 +69,8 @@ const kibanaOrTaskManagerIndex = (index: string, kibanaConfigIndex: string) => {
6069
return index === kibanaConfigIndex ? '.kibana' : '.kibana_task_manager';
6170
};
6271

63-
export class CoreUsageDataService implements CoreService<void, CoreUsageDataStart> {
72+
export class CoreUsageDataService implements CoreService<CoreUsageDataSetup, CoreUsageDataStart> {
73+
private logger: Logger;
6474
private elasticsearchConfig?: ElasticsearchConfigType;
6575
private configService: CoreContext['configService'];
6676
private httpConfig?: HttpConfigType;
@@ -69,8 +79,10 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
6979
private stop$: Subject<void>;
7080
private opsMetrics?: OpsMetrics;
7181
private kibanaConfig?: KibanaConfigType;
82+
private coreUsageStatsClient?: CoreUsageStatsClient;
7283

7384
constructor(core: CoreContext) {
85+
this.logger = core.logger.get('core-usage-stats-service');
7486
this.configService = core.configService;
7587
this.stop$ = new Subject();
7688
}
@@ -130,8 +142,15 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
130142
throw new Error('Unable to read config values. Ensure that setup() has completed.');
131143
}
132144

145+
if (!this.coreUsageStatsClient) {
146+
throw new Error(
147+
'Core usage stats client is not initialized. Ensure that setup() has completed.'
148+
);
149+
}
150+
133151
const es = this.elasticsearchConfig;
134152
const soUsageData = await this.getSavedObjectIndicesUsageData(savedObjects, elasticsearch);
153+
const coreUsageStatsData = await this.coreUsageStatsClient.getUsageStats();
135154

136155
const http = this.httpConfig;
137156
return {
@@ -225,10 +244,11 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
225244
services: {
226245
savedObjects: soUsageData,
227246
},
247+
...coreUsageStatsData,
228248
};
229249
}
230250

231-
setup({ metrics }: SetupDeps) {
251+
setup({ metrics, savedObjectsStartPromise }: SetupDeps) {
232252
metrics
233253
.getOpsMetrics$()
234254
.pipe(takeUntil(this.stop$))
@@ -268,6 +288,24 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
268288
.subscribe((config) => {
269289
this.kibanaConfig = config;
270290
});
291+
292+
const internalRepositoryPromise = savedObjectsStartPromise.then((savedObjects) =>
293+
savedObjects.createInternalRepository([CORE_USAGE_STATS_TYPE])
294+
);
295+
296+
const registerType = (typeRegistry: SavedObjectTypeRegistry) => {
297+
typeRegistry.registerType(coreUsageStatsType);
298+
};
299+
300+
const getClient = () => {
301+
const debugLogger = (message: string) => this.logger.debug(message);
302+
303+
return new CoreUsageStatsClient(debugLogger, internalRepositoryPromise);
304+
};
305+
306+
this.coreUsageStatsClient = getClient();
307+
308+
return { registerType, getClient } as CoreUsageDataSetup;
271309
}
272310

273311
start({ savedObjects, elasticsearch }: StartDeps) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { SavedObjectsType } from '../saved_objects';
21+
import { CORE_USAGE_STATS_TYPE } from './constants';
22+
23+
/** @internal */
24+
export const coreUsageStatsType: SavedObjectsType = {
25+
name: CORE_USAGE_STATS_TYPE,
26+
hidden: true,
27+
namespaceType: 'agnostic',
28+
mappings: {
29+
dynamic: false, // we aren't querying or aggregating over this data, so we don't need to specify any fields
30+
properties: {},
31+
},
32+
};

0 commit comments

Comments
 (0)