Skip to content

Commit

Permalink
[Tock Studio] Export Faqs in csv format (#1765)
Browse files Browse the repository at this point in the history
* [Tock Studio] Export Faqs in csv format

* Avoid mutating the supplied data collection if it is to be used directly for a csv export.
  • Loading branch information
rkuffer authored Nov 18, 2024
1 parent 1fa8c21 commit d45d788
Show file tree
Hide file tree
Showing 10 changed files with 555 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ <h1 class="flex-grow-1">FAQs stories</h1>

<section class="grid-actions">
<button
[disabled]="loading.list || !faqs?.length"
nbButton
ghost
shape="round"
nbTooltip="Export all faqs"
(click)="download()"
(click)="openExport()"
>
<nb-icon icon="download"></nb-icon>
</button>
Expand Down Expand Up @@ -35,7 +36,7 @@ <h1 class="flex-grow-1">FAQs stories</h1>
</section>
</div>

<section [class.grid]="isSidePanelOpen.edit || isSidePanelOpen.settings">
<section [class.grid]="isSidePanelOpen.edit || isSidePanelOpen.settings || isSidePanelOpen.export">
<tock-no-data-found
*ngIf="configurations?.length === 0"
title="No bot configuration detected"
Expand Down Expand Up @@ -103,6 +104,15 @@ <h1 class="flex-grow-1">FAQs stories</h1>
class="aside"
(onClose)="closeSidePanel()"
></tock-faq-management-settings>

<tock-data-export
*ngIf="isSidePanelOpen.export"
class="aside"
[data]="faqs"
exportFileNameType="Faqs"
[searchQuery]="getExportSearchQuery()"
(onClose)="closeSidePanel()"
></tock-data-export>
</section>

<tock-scroll-top-button></tock-scroll-top-button>
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,6 @@ section {
height: fit-content;
}

section {
&.grid-actions {
display: grid;
grid-gap: 0.5rem;
grid-auto-flow: column;
align-items: center;
justify-content: end;
}

& nb-card {
height: min-content;
}

& .alert-config {
height: fit-content;
}
}

.loading {
min-height: 50px;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Location } from '@angular/common';
import { NbToastrService } from '@nebular/theme';
import { Subject } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';

import { BotApplicationConfiguration } from '../../core/model/configuration';
Expand Down Expand Up @@ -36,7 +36,8 @@ export class FaqManagementComponent implements OnInit, OnDestroy {

isSidePanelOpen = {
edit: false,
settings: false
settings: false,
export: false
};

loading = {
Expand Down Expand Up @@ -179,6 +180,7 @@ export class FaqManagementComponent implements OnInit, OnDestroy {
closeSidePanel(): void {
this.isSidePanelOpen.settings = false;
this.isSidePanelOpen.edit = false;
this.isSidePanelOpen.export = false;
this.faqEdit = undefined;
}

Expand Down Expand Up @@ -228,10 +230,12 @@ export class FaqManagementComponent implements OnInit, OnDestroy {
this.faqEdit._initQuestion = initQuestion;
}

this.closeSidePanel();
this.isSidePanelOpen.edit = true;
}

editFaq(faq: FaqDefinitionExtended) {
this.closeSidePanel();
this.faqEdit = faq;
this.isSidePanelOpen.edit = true;
}
Expand Down Expand Up @@ -313,33 +317,40 @@ export class FaqManagementComponent implements OnInit, OnDestroy {
}
});
} else {
this.closeSidePanel();
this.isSidePanelOpen.settings = true;
}
}

download() {
let query: PaginatedQuery = this.stateService.createPaginatedQuery(0, 9999);
const request = this.toSearchQuery(query);

this.rest
.post('/faq/search', request)
.pipe(takeUntil(this.destroy))
.subscribe((faqsResult: PaginatedFaqResult) => {
const jsonBlob = new Blob([JSON.stringify(faqsResult.rows)], {
type: 'application/json'
openExport(): void {
if (this.faqSettingsComponent) {
this.faqSettingsComponent
.close()
.pipe(take(1))
.subscribe((res) => {
if (res != 'cancel') {
this.isSidePanelOpen.export = true;
}
});
} else if (this.faqEditComponent) {
this.faqEditComponent
.close()
.pipe(take(1))
.subscribe((res) => {
if (res != 'cancel') {
this.isSidePanelOpen.export = true;
}
});
} else {
this.isSidePanelOpen.export = true;
}
}

const exportFileName = getExportFileName(
this.stateService.currentApplication.namespace,
this.stateService.currentApplication.name,
'Faqs',
'json'
);

saveAs(jsonBlob, exportFileName);
getExportSearchQuery(): Observable<PaginatedFaqResult> {
let query: PaginatedQuery = this.stateService.createPaginatedQuery(0, 9999);
const request = this.toSearchQuery(query);

this.toastrService.show(`Faqs dump provided`, 'Faqs dump', { duration: 3000, status: 'success' });
});
return this.rest.post('/faq/search', request);
}

ngOnDestroy() {
Expand Down
4 changes: 3 additions & 1 deletion bot/admin/web/src/app/faq/faq.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
NbFormFieldModule,
NbIconModule,
NbInputModule,
NbRadioModule,
NbRouteTabsetModule,
NbSelectModule,
NbSpinnerModule,
Expand Down Expand Up @@ -60,7 +61,8 @@ import { FaqTabsComponent } from './faq-tabs.component';
InfiniteScrollModule,
BotAnalyticsModule,
NbRouteTabsetModule,
NbToggleModule
NbToggleModule,
NbRadioModule
],
declarations: [
FaqManagementComponent,
Expand Down
9 changes: 6 additions & 3 deletions bot/admin/web/src/app/shared/bot-shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ import {
SentencesGenerationOptionsComponent,
InfoButtonComponent,
SelectBotComponent,
DateRangeCalendarComponent
DateRangeCalendarComponent,
DataExportComponent
} from './components';

import { AutofocusDirective } from './directives';
Expand Down Expand Up @@ -154,7 +155,8 @@ import { ScrollComponent } from '../scroll/scroll.component';
SentencesGenerationOptionsComponent,
SentencesGenerationListComponent,
SentencesGenerationComponent,
ScrollComponent
ScrollComponent,
DataExportComponent
],
exports: [
SelectBotComponent,
Expand All @@ -181,7 +183,8 @@ import { ScrollComponent } from '../scroll/scroll.component';
AutofocusDirective,
StickyMenuComponent,
AiSettingsEngineConfigParamInputComponent,
SentencesGenerationComponent
SentencesGenerationComponent,
DataExportComponent
],
providers: [BotSharedService, AnalyticsService]
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<form [formGroup]="form">
<nb-card
class="mb-0"
[nbSpinner]="loading"
>
<nb-card-header>
<div class="d-flex gap-1 justify-content-between align-items-start">
<h4>{{ exportFileNameType }} export</h4>
<button
type="button"
nbButton
ghost
shape="round"
nbTooltip="Close"
(click)="close()"
data-testid="close-button"
>
<nb-icon icon="x-lg"></nb-icon>
</button>
</div>
</nb-card-header>

<nb-card-body>
<tock-form-control
label="Export format"
name="format"
[controls]="format"
[showError]="isSubmitted"
[required]="true"
[hasMargin]="false"
class="d-block mb-2"
>
<nb-radio-group
class="d-flex"
formControlName="format"
name="format"
>
<nb-radio [value]="formats.json"> JSON </nb-radio>
<nb-radio [value]="formats.csv"> CSV </nb-radio>
</nb-radio-group>
</tock-form-control>

<ng-container *ngIf="format.value === formats.csv">
<div class="row">
<div class="col-sm-6 col-12">
<tock-form-control
label="Delimiter"
name="delimiter"
[controls]="delimiter"
[showError]="isSubmitted"
[required]="true"
[hasMargin]="false"
class="d-block mb-2"
>
<nb-select
formControlName="delimiter"
name="delimiter"
size="small"
fullWidth
>
<nb-option
*ngFor="let separator of delimiters | keyvalue"
[value]="separator.value"
>{{ separator.key }}</nb-option
>
</nb-select>
</tock-form-control>
</div>
<div class="col-sm-6 col-12">
<tock-form-control
label="Lists delimiter"
name="listDelimiter"
[controls]="listDelimiter"
[showError]="isSubmitted"
[required]="true"
[hasMargin]="false"
class="d-block mb-2"
>
<nb-select
formControlName="listDelimiter"
name="listDelimiter"
size="small"
fullWidth
>
<nb-option
*ngFor="let separator of listDelimiters | keyvalue"
[value]="separator.value"
>{{ separator.key }}</nb-option
>
</nb-select>
</tock-form-control>
</div>
</div>

<tock-form-control
label="Columns to export"
name="columns"
[controls]="columns"
[showError]="isSubmitted"
[required]="true"
[hasMargin]="false"
>
<div formArrayName="columns">
<div *ngFor="let control of columns.controls; index as i; trackBy: trackByFn">
<div
[formGroupName]="i"
class="d-flex gap-1 align-items-center justify-content-between"
>
<nb-checkbox formControlName="selected"> {{ columns.controls[i].get('name').value }} </nb-checkbox>

<div
*ngIf="columns.controls[i].get('selected').value"
class="index-change-buttons d-flex align-items-center justify-content-between"
>
<div class="w-50 text-center">
<button
*ngIf="i > 0"
type="button"
nbButton
ghost
size="tiny"
nbTooltip="Reduce column index"
class="index-change-button"
(click)="changeColumnIndex(-1, i)"
>
<nb-icon
icon="chevron-up-outline"
pack="nebular-essentials"
class="pointer"
></nb-icon>
</button>
</div>

<div class="w-50 text-center">
<button
*ngIf="canIncreaseColumnIndex(i)"
type="button"
nbButton
ghost
size="tiny"
nbTooltip="Increase column index"
class="index-change-button"
(click)="changeColumnIndex(1, i)"
>
<nb-icon
icon="chevron-down-outline"
pack="nebular-essentials"
class="pointer"
></nb-icon>
</button>
</div>
</div>
</div>
</div>
</div>
</tock-form-control>
</ng-container>
</nb-card-body>

<nb-card-footer>
<div class="grid-button">
<button
type="button"
nbButton
outline
status="primary"
size="small"
(click)="close()"
data-testid="cancel-button"
>
Cancel
</button>
<button
type="button"
nbButton
status="primary"
size="small"
[disabled]="!canSave"
(click)="export()"
>
Export
</button>
</div>
</nb-card-footer>
</nb-card>
</form>
Loading

0 comments on commit d45d788

Please sign in to comment.