Skip to content

Commit e7a9428

Browse files
authored
[Tock Studio] Export and import of ia gen settings (#1760)
* Wip * Rag settings export * Rag settings import * Sentence generation settings export and import * Observability settings export and import * Vector DB settings export and import
1 parent d45d788 commit e7a9428

17 files changed

+1273
-85
lines changed

bot/admin/web/src/app/configuration/observability-settings/models/providers-configuration.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface ProvidersConfigurationParam {
99
source?: string[];
1010
inputScale?: 'default' | 'fullwidth';
1111
defaultValue?: string;
12+
confirmExport?: boolean;
1213
}
1314

1415
export interface ProvidersConfiguration {
@@ -23,7 +24,7 @@ export const ProvidersConfigurations: ProvidersConfiguration[] = [
2324
key: ObservabilityProvider.Langfuse,
2425
params: [
2526
{ key: 'publicKey', label: 'Public key', type: 'obfuscated' },
26-
{ key: 'secretKey', label: 'Secret key', type: 'obfuscated' },
27+
{ key: 'secretKey', label: 'Secret key', type: 'obfuscated', confirmExport: true },
2728
{ key: 'url', label: 'Url', type: 'obfuscated' }
2829
]
2930
}

bot/admin/web/src/app/configuration/observability-settings/observability-settings.component.html

+133
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@
33
<h1 class="flex-grow-1">Observability settings</h1>
44

55
<section class="grid-actions">
6+
<button
7+
[disabled]="!hasExportableData"
8+
nbButton
9+
ghost
10+
shape="round"
11+
nbTooltip="Export observability settings dump"
12+
(click)="exportSettings()"
13+
>
14+
<nb-icon icon="download"></nb-icon>
15+
</button>
16+
<button
17+
nbButton
18+
ghost
19+
shape="round"
20+
nbTooltip="Import observability settings dump"
21+
(click)="importSettings()"
22+
>
23+
<nb-icon icon="upload"></nb-icon>
24+
</button>
25+
626
<button
727
*ngIf="settingsBackup && form.dirty"
828
nbButton
@@ -115,3 +135,116 @@ <h5 class="section-title mt-2">Settings deletion</h5>
115135
</nb-card-body>
116136
</nb-card>
117137
</form>
138+
139+
<ng-template #exportConfirmationModal>
140+
<nb-card class="help-modal">
141+
<nb-card-header class="d-flex justify-content-between align-items-start gap-1">
142+
Confirmation of sensitive data export
143+
<button
144+
nbButton
145+
ghost
146+
shape="round"
147+
nbTooltip="Cancel"
148+
(click)="closeExportConfirmationModal()"
149+
>
150+
<nb-icon icon="x-lg"></nb-icon>
151+
</button>
152+
</nb-card-header>
153+
154+
<nb-card-body>
155+
<div class="mb-2">Include the following sensitive data:</div>
156+
<div *ngFor="let sensitiveParam of sensitiveParams">
157+
<nb-checkbox
158+
status="basic"
159+
[(ngModel)]="sensitiveParam.include"
160+
>
161+
{{ sensitiveParam.label }}
162+
{{ sensitiveParam.param.label }}
163+
</nb-checkbox>
164+
</div>
165+
</nb-card-body>
166+
167+
<nb-card-footer class="card-footer-actions">
168+
<button
169+
nbButton
170+
ghost
171+
size="small"
172+
(click)="closeExportConfirmationModal()"
173+
>
174+
Cancel
175+
</button>
176+
<button
177+
type="button"
178+
nbButton
179+
status="primary"
180+
size="small"
181+
(click)="confirmExportSettings()"
182+
>
183+
Export
184+
</button>
185+
</nb-card-footer>
186+
</nb-card>
187+
</ng-template>
188+
189+
<ng-template #importModal>
190+
<nb-card class="help-modal">
191+
<nb-card-header class="d-flex justify-content-between align-items-start gap-1">
192+
Import observability settings dump
193+
<button
194+
nbButton
195+
ghost
196+
shape="round"
197+
nbTooltip="Cancel"
198+
(click)="closeImportModal()"
199+
>
200+
<nb-icon icon="x-lg"></nb-icon>
201+
</button>
202+
</nb-card-header>
203+
204+
<nb-card-body>
205+
<form
206+
[formGroup]="importForm"
207+
(submit)="submitImportSettings()"
208+
>
209+
<tock-form-control
210+
label="Observability settings dump file"
211+
name="importFile"
212+
[required]="true"
213+
[controls]="fileSource"
214+
[showError]="isImportSubmitted"
215+
>
216+
<tock-file-upload
217+
id="importFile"
218+
formControlName="fileSource"
219+
[autofocus]="true"
220+
[fullWidth]="true"
221+
[multiple]="false"
222+
[fileTypeAccepted]="['json']"
223+
></tock-file-upload>
224+
</tock-form-control>
225+
</form>
226+
</nb-card-body>
227+
228+
<nb-card-footer class="card-footer-actions">
229+
<button
230+
nbButton
231+
ghost
232+
size="small"
233+
(click)="closeImportModal()"
234+
>
235+
Cancel
236+
</button>
237+
<button
238+
type="button"
239+
nbButton
240+
status="primary"
241+
size="small"
242+
(click)="submitImportSettings()"
243+
>
244+
Import
245+
</button>
246+
</nb-card-footer>
247+
</nb-card>
248+
</ng-template>
249+
250+
<tock-scroll-top-button></tock-scroll-top-button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.grid-actions {
2+
display: grid;
3+
grid-gap: 0.5rem;
4+
grid-auto-flow: column;
5+
align-items: center;
6+
justify-content: end;
7+
}

bot/admin/web/src/app/configuration/observability-settings/observability-settings.component.ts

+165-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
import { Component, OnDestroy, OnInit } from '@angular/core';
1+
import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
22
import { StateService } from '../../core-nlp/state.service';
33
import { RestService } from '../../core-nlp/rest/rest.service';
44
import { NbDialogService, NbToastrService, NbWindowService } from '@nebular/theme';
55
import { BotConfigurationService } from '../../core/bot-configuration.service';
66
import { Observable, Subject, debounceTime, takeUntil } from 'rxjs';
77
import { BotApplicationConfiguration } from '../../core/model/configuration';
88
import { FormControl, FormGroup, Validators } from '@angular/forms';
9-
import { ObservabilityProvider, ProvidersConfiguration, ProvidersConfigurations } from './models/providers-configuration';
9+
import {
10+
ObservabilityProvider,
11+
ProvidersConfiguration,
12+
ProvidersConfigurationParam,
13+
ProvidersConfigurations
14+
} from './models/providers-configuration';
1015
import { ObservabilitySettings } from './models/observability-settings';
11-
import { deepCopy } from '../../shared/utils';
16+
import { deepCopy, getExportFileName, readFileAsText } from '../../shared/utils';
1217
import { ChoiceDialogComponent, DebugViewerWindowComponent } from '../../shared/components';
18+
import { saveAs } from 'file-saver-es';
19+
import { FileValidators } from '../../shared/validators';
1320

1421
interface ObservabilitySettingsForm {
1522
id: FormControl<string>;
@@ -36,6 +43,9 @@ export class ObservabilitySettingsComponent implements OnInit, OnDestroy {
3643

3744
settingsBackup: ObservabilitySettings;
3845

46+
@ViewChild('exportConfirmationModal') exportConfirmationModal: TemplateRef<any>;
47+
@ViewChild('importModal') importModal: TemplateRef<any>;
48+
3949
constructor(
4050
private state: StateService,
4151
private rest: RestService,
@@ -59,9 +69,14 @@ export class ObservabilitySettingsComponent implements OnInit, OnDestroy {
5969

6070
this.botConfiguration.configurations.pipe(takeUntil(this.destroy$)).subscribe((confs: BotApplicationConfiguration[]) => {
6171
delete this.settingsBackup;
72+
73+
// Reset form on configuration change
74+
this.form.reset();
75+
// Reset formGroup control too, if any
76+
this.resetFormGroupControls();
77+
6278
this.loading = true;
6379
this.configurations = confs;
64-
this.form.reset();
6580

6681
if (confs.length) {
6782
this.getObservabilitySettingsLoader().subscribe((res) => {
@@ -136,10 +151,7 @@ export class ObservabilitySettingsComponent implements OnInit, OnDestroy {
136151

137152
if (requiredConfiguration) {
138153
// Purge existing controls that may contain values incompatible with a new control with the same name if provider change
139-
const existingGroupKeys = Object.keys(this.form.controls['setting'].controls);
140-
existingGroupKeys.forEach((key) => {
141-
this.form.controls['setting'].removeControl(key);
142-
});
154+
this.resetFormGroupControls();
143155

144156
requiredConfiguration.params.forEach((param) => {
145157
this.form.controls['setting'].addControl(param.key, new FormControl(param.defaultValue, Validators.required));
@@ -149,6 +161,13 @@ export class ObservabilitySettingsComponent implements OnInit, OnDestroy {
149161
}
150162
}
151163

164+
resetFormGroupControls() {
165+
const existingGroupKeys = Object.keys(this.form.controls['setting'].controls);
166+
existingGroupKeys.forEach((key) => {
167+
this.form.controls['setting'].removeControl(key);
168+
});
169+
}
170+
152171
cancel(): void {
153172
this.initForm(this.settingsBackup);
154173
}
@@ -196,6 +215,144 @@ export class ObservabilitySettingsComponent implements OnInit, OnDestroy {
196215
}
197216
}
198217

218+
get hasExportableData(): boolean {
219+
if (this.observabilityProvider.value) return true;
220+
221+
const formValue: ObservabilitySettings = deepCopy(this.form.value) as unknown as ObservabilitySettings;
222+
223+
return Object.values(formValue).some((entry) => {
224+
return entry && (typeof entry !== 'object' || Object.keys(entry).length !== 0);
225+
});
226+
}
227+
228+
sensitiveParams: { label: string; key: string; include: boolean; param: ProvidersConfigurationParam }[];
229+
230+
exportSettings() {
231+
this.sensitiveParams = [];
232+
233+
const shouldConfirm =
234+
this.observabilityProvider.value &&
235+
this.currentObservabilityProvider.params.some((entry) => {
236+
return entry.confirmExport;
237+
});
238+
239+
if (shouldConfirm) {
240+
this.currentObservabilityProvider.params.forEach((entry) => {
241+
if (entry.confirmExport) {
242+
this.sensitiveParams.push({ label: 'Observability provider', key: 'setting', include: false, param: entry });
243+
}
244+
});
245+
246+
this.exportConfirmationModalRef = this.nbDialogService.open(this.exportConfirmationModal);
247+
} else {
248+
this.downloadSettings();
249+
}
250+
}
251+
252+
exportConfirmationModalRef;
253+
254+
closeExportConfirmationModal() {
255+
this.exportConfirmationModalRef.close();
256+
}
257+
258+
confirmExportSettings() {
259+
this.downloadSettings();
260+
this.closeExportConfirmationModal();
261+
}
262+
263+
downloadSettings() {
264+
const formValue: ObservabilitySettings = deepCopy(this.form.value) as unknown as ObservabilitySettings;
265+
delete formValue['observabilityProvider'];
266+
delete formValue['id'];
267+
delete formValue['enabled'];
268+
269+
if (this.sensitiveParams?.length) {
270+
this.sensitiveParams.forEach((sensitiveParam) => {
271+
if (!sensitiveParam.include) {
272+
delete formValue[sensitiveParam.key][sensitiveParam.param.key];
273+
}
274+
});
275+
}
276+
277+
const jsonBlob = new Blob([JSON.stringify(formValue)], {
278+
type: 'application/json'
279+
});
280+
281+
const exportFileName = getExportFileName(
282+
this.state.currentApplication.namespace,
283+
this.state.currentApplication.name,
284+
'Observability settings',
285+
'json'
286+
);
287+
288+
saveAs(jsonBlob, exportFileName);
289+
290+
this.toastrService.show(`Observability settings dump provided`, 'Observability settings dump', {
291+
duration: 3000,
292+
status: 'success'
293+
});
294+
}
295+
296+
importModalRef;
297+
298+
importSettings() {
299+
this.isImportSubmitted = false;
300+
this.importForm.reset();
301+
this.importModalRef = this.nbDialogService.open(this.importModal);
302+
}
303+
304+
closeImportModal() {
305+
this.importModalRef.close();
306+
}
307+
308+
isImportSubmitted: boolean = false;
309+
310+
importForm: FormGroup = new FormGroup({
311+
fileSource: new FormControl<File[]>([], {
312+
nonNullable: true,
313+
validators: [Validators.required, FileValidators.mimeTypeSupported(['application/json'])]
314+
})
315+
});
316+
317+
get fileSource(): FormControl {
318+
return this.importForm.get('fileSource') as FormControl;
319+
}
320+
321+
get canSaveImport(): boolean {
322+
return this.isImportSubmitted ? this.importForm.valid : this.importForm.dirty;
323+
}
324+
325+
submitImportSettings() {
326+
this.isImportSubmitted = true;
327+
if (this.canSaveImport) {
328+
const file = this.fileSource.value[0];
329+
330+
readFileAsText(file).then((fileContent) => {
331+
const settings = JSON.parse(fileContent.data);
332+
333+
const hasCompatibleProvider =
334+
settings.setting?.provider && Object.values(ObservabilityProvider).includes(settings.setting.provider);
335+
336+
if (!hasCompatibleProvider) {
337+
this.toastrService.show(
338+
`The file supplied does not reference a compatible provider. Please check the file.`,
339+
'Observability settings import fails',
340+
{
341+
duration: 6000,
342+
status: 'danger'
343+
}
344+
);
345+
return;
346+
}
347+
348+
this.initForm(settings);
349+
this.form.markAsDirty();
350+
351+
this.closeImportModal();
352+
});
353+
}
354+
}
355+
199356
confirmSettingsDeletion() {
200357
const confirmAction = 'Delete';
201358
const cancelAction = 'Cancel';

0 commit comments

Comments
 (0)