-
Notifications
You must be signed in to change notification settings - Fork 8.2k
/
attribute_service.tsx
199 lines (185 loc) · 6.97 KB
/
attribute_service.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { get, omit } from 'lodash';
import { NotificationsStart } from '@kbn/core/public';
import {
SavedObjectSaveModal,
OnSaveProps,
SaveResult,
showSaveModal,
} from '@kbn/saved-objects-plugin/public';
import {
EmbeddableInput,
SavedObjectEmbeddableInput,
isSavedObjectEmbeddableInput,
EmbeddableFactory,
} from '..';
/**
* The attribute service is a shared, generic service that embeddables can use to provide the functionality
* required to fulfill the requirements of the ReferenceOrValueEmbeddable interface. The attribute_service
* can also be used as a higher level wrapper to transform an embeddable input shape that references a saved object
* into an embeddable input shape that contains that saved object's attributes by value.
*/
export const ATTRIBUTE_SERVICE_KEY = 'attributes';
export interface GenericAttributes {
title: string;
}
export interface AttributeServiceUnwrapResult<
SavedObjectAttributes extends GenericAttributes,
MetaInfo extends unknown = unknown
> {
attributes: SavedObjectAttributes;
metaInfo?: MetaInfo;
}
export interface AttributeServiceOptions<
SavedObjectAttributes extends GenericAttributes,
MetaInfo extends unknown = unknown
> {
saveMethod: (
attributes: SavedObjectAttributes,
savedObjectId?: string
) => Promise<{ id?: string } | { error: Error }>;
checkForDuplicateTitle: (props: OnSaveProps) => Promise<boolean>;
unwrapMethod?: (
savedObjectId: string
) => Promise<AttributeServiceUnwrapResult<SavedObjectAttributes, MetaInfo>>;
}
export class AttributeService<
SavedObjectAttributes extends { title: string },
ValType extends EmbeddableInput & {
[ATTRIBUTE_SERVICE_KEY]: SavedObjectAttributes;
} = EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: SavedObjectAttributes },
RefType extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput,
MetaInfo extends unknown = unknown
> {
private embeddableFactory;
constructor(
private type: string,
private toasts: NotificationsStart['toasts'],
private options: AttributeServiceOptions<SavedObjectAttributes, MetaInfo>,
getEmbeddableFactory?: (embeddableFactoryId: string) => EmbeddableFactory
) {
if (getEmbeddableFactory) {
const factory = getEmbeddableFactory(this.type);
this.embeddableFactory = factory;
}
}
private async defaultUnwrapMethod(
input: RefType
): Promise<AttributeServiceUnwrapResult<SavedObjectAttributes, MetaInfo>> {
return Promise.resolve({ attributes: { ...(input as unknown as SavedObjectAttributes) } });
}
public async unwrapAttributes(
input: RefType | ValType
): Promise<AttributeServiceUnwrapResult<SavedObjectAttributes, MetaInfo>> {
if (this.inputIsRefType(input)) {
return this.options.unwrapMethod
? await this.options.unwrapMethod(input.savedObjectId)
: await this.defaultUnwrapMethod(input);
}
return { attributes: (input as ValType)[ATTRIBUTE_SERVICE_KEY] };
}
public async wrapAttributes(
newAttributes: SavedObjectAttributes,
useRefType: boolean,
input?: ValType | RefType
): Promise<Omit<ValType | RefType, 'id'>> {
const originalInput = input ? input : {};
const savedObjectId =
input && this.inputIsRefType(input)
? (input as SavedObjectEmbeddableInput).savedObjectId
: undefined;
if (!useRefType) {
return { [ATTRIBUTE_SERVICE_KEY]: newAttributes } as ValType;
}
try {
const savedItem = await this.options.saveMethod(newAttributes, savedObjectId);
if ('id' in savedItem) {
return { ...originalInput, savedObjectId: savedItem.id } as RefType;
}
return { ...originalInput } as RefType;
} catch (error) {
this.toasts.addDanger({
title: i18n.translate('embeddableApi.attributeService.saveToLibraryError', {
defaultMessage: `An error occurred while saving. Error: {errorMessage}`,
values: {
errorMessage: error.message,
},
}),
'data-test-subj': 'attributeServiceSaveFailure',
});
return Promise.reject({ error });
}
}
inputIsRefType = (input: ValType | RefType): input is RefType => {
return isSavedObjectEmbeddableInput(input);
};
getInputAsValueType = async (input: ValType | RefType): Promise<ValType> => {
if (!this.inputIsRefType(input)) {
return input as ValType;
}
const { attributes } = await this.unwrapAttributes(input);
const { savedObjectId, ...originalInputToPropagate } = input;
return {
...originalInputToPropagate,
// by value visualizations should not have default titles and/or descriptions
...{ attributes: omit(attributes, ['title', 'description']) },
} as unknown as ValType;
};
getInputAsRefType = async (
input: ValType | RefType,
saveOptions?: { showSaveModal: boolean; saveModalTitle?: string } | { title: string }
): Promise<RefType> => {
if (this.inputIsRefType(input)) {
return input;
}
return new Promise<RefType>((resolve, reject) => {
const onSave = async (props: OnSaveProps): Promise<SaveResult> => {
await this.options.checkForDuplicateTitle(props);
try {
const newAttributes = { ...(input as ValType)[ATTRIBUTE_SERVICE_KEY] };
newAttributes.title = props.newTitle;
const wrappedInput = (await this.wrapAttributes(
newAttributes,
true
)) as unknown as RefType;
// Remove unneeded attributes from the original input. Note that the original panel title
// is removed in favour of the new attributes title
const newInput = omit(input, [ATTRIBUTE_SERVICE_KEY, 'title']);
// Combine input and wrapped input to preserve any passed in explicit Input
resolve({ ...newInput, ...wrappedInput });
return { id: wrappedInput.savedObjectId };
} catch (error) {
reject(error);
return { error };
}
};
if (saveOptions && (saveOptions as { showSaveModal: boolean }).showSaveModal) {
showSaveModal(
<SavedObjectSaveModal
onSave={onSave}
onClose={() => {}}
title={get(
saveOptions,
'saveModalTitle',
(input as ValType)[ATTRIBUTE_SERVICE_KEY].title
)}
showCopyOnSave={false}
objectType={
this.embeddableFactory ? this.embeddableFactory.getDisplayName() : this.type
}
showDescription={false}
/>
);
}
});
};
}