Skip to content

Commit

Permalink
feat(step-form): enable to use form component alone or with query bui…
Browse files Browse the repository at this point in the history
…lder and store
  • Loading branch information
alice-sevin committed Sep 25, 2024
1 parent 478c04e commit dd9233d
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 85 deletions.
12 changes: 4 additions & 8 deletions ui/src/components/QueryBuilder.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<template>
<div class="query-builder" data-cy="weaverbird-query-builder" v-if="pipeline">
<transition v-if="isEditingStep" name="slide-right" mode="out-in">
<component
<StepForm
key="stepForm"
:is="formComponent"
ref="step"
:name="currentStepFormName"
:initialStepValue="stepFormInitialValue"
:stepFormDefaults="stepFormDefaults"
:isStepCreation="isStepCreation"
:backendError="backendError"
@back="closeStepForm"
@formSaved="saveStep"
Expand Down Expand Up @@ -44,11 +43,12 @@ import { Action, Getter, State } from 'pinia-class';
import { VQBModule, type VQBActions } from '@/store';
import { version } from '../../package.json';
import StepFormsComponents from './stepforms';
import StoreStepFormComponent from './stepforms/StoreStepFormComponent.vue';
@Component({
name: 'query-builder',
components: {
StepForm: StoreStepFormComponent,
Pipeline: PipelineComponent,
FAIcon,
},
Expand Down Expand Up @@ -76,10 +76,6 @@ export default class QueryBuilder extends Vue {
return this.stepFormInitialValue === undefined;
}
get formComponent() {
return StepFormsComponents[this.currentStepFormName];
}
get backendError(): string | undefined {
return this.isStepCreation ? undefined : this.editedStepBackendError;
}
Expand Down
116 changes: 116 additions & 0 deletions ui/src/components/stepforms/StepFormComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<template>
<component
key="stepForm"
:is="formComponent"
ref="step"
:translator="translator"
:initialStepValue="initialStepValue"
:isStepCreation="isStepCreation"
:columnTypes="columnTypes"
:backendError="backendError"
:availableVariables="availableVariables"
:variableDelimiters="variableDelimiters"
:trustedVariableDelimiters="trustedVariableDelimiters"
:variables="variables"
:availableDomains="availableDomains"
:unjoinableDomains="unjoinableDomains"
:selectedColumns="selectedColumns"
:interpolateFunc="interpolateFunc"
:getColumnNamesFromPipeline="getColumnNamesFromPipeline"
@back="back"
@formSaved="formSaved"
@setSelectedColumns="setSelectedColumns"
/>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import type { PipelineStep, PipelineStepName, ReferenceToExternalQuery } from '@/lib/steps';
import StepFormsComponents from './index';
import { VariableDelimiters, VariablesBucket } from '@/types';
import { InterpolateFunction, ScopeContext } from '@/lib/templating';
import { ColumnTypeMapping } from '@/lib/dataset';
/*
StepComponent to use outside of QueryBuilder context, it do not need pinia store to handle selectedColumns
*/
@Component({
name: 'step-from-component',
})
export default class StepFormComponent extends Vue {
@Prop({
type: String,
required: true,
})
name!: PipelineStepName;
@Prop({ type: String, default: 'pandas' })
translator!: string;
@Prop({
type: Object,
required: false,
default: undefined,
})
initialStepValue?: Record<string, any>;
@Prop({ type: Object, default: undefined })
stepFormDefaults!: Record<string, any>;
@Prop({ type: String, default: undefined })
backendError?: string;
@Prop({ type: Object, default: () => ({}) })
columnTypes!: ColumnTypeMapping;
@Prop()
availableVariables?: VariablesBucket;
@Prop()
variableDelimiters?: VariableDelimiters;
@Prop()
trustedVariableDelimiters?: VariableDelimiters;
@Prop({ type: Object, required: false, default: () => ({}) })
variables!: ScopeContext;
@Prop({ type: Array, default: () => [] })
availableDomains!: { name: string; uid: string }[];
@Prop({ type: Array, default: () => [] })
unjoinableDomains!: { name: string; uid: string }[];
@Prop({ type: Function, required: true })
interpolateFunc!: InterpolateFunction;
@Prop({ type: Function, required: true })
getColumnNamesFromPipeline!: (
pipelineNameOrDomain: string | ReferenceToExternalQuery,
) => Promise<string[] | undefined>;
get isStepCreation() {
return this.initialStepValue === undefined;
}
get formComponent() {
return StepFormsComponents[this.name];
}
back() {
this.$emit('back');
}
formSaved(step: PipelineStep) {
this.$emit('formSaved', step);
}
selectedColumns: string[] = [];
setSelectedColumns({ column }: { column: string | undefined }) {
if (!!column && column !== this.selectedColumns[0]) {
this.selectedColumns = [column];
}
}
}
</script>
91 changes: 91 additions & 0 deletions ui/src/components/stepforms/StoreStepFormComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<component
key="stepForm"
:is="formComponent"
ref="step"
:translator="translator"
:initialStepValue="initialStepValue"
:isStepCreation="isStepCreation"
:columnTypes="columnTypes"
:backendError="backendError"
:availableVariables="availableVariables"
:variableDelimiters="variableDelimiters"
:trustedVariableDelimiters="trustedVariableDelimiters"
:variables="variables"
:availableDomains="availableDomains"
:unjoinableDomains="unjoinableDomains"
:selectedColumns="selectedColumns"
:interpolateFunc="interpolateFunc"
:getColumnNamesFromPipeline="getColumnNamesFromPipeline"
@back="back"
@formSaved="formSaved"
@setSelectedColumns="setSelectedColumns"
/>
</template>

<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import type { PipelineStep, PipelineStepName } from '@/lib/steps';
import StepFormsComponents from './index';
import { VariableDelimiters, VariablesBucket } from '@/types';
import { InterpolateFunction, ScopeContext } from '@/lib/templating';
import { ColumnTypeMapping } from '@/lib/dataset';
import { Action, Getter, State } from 'pinia-class';
import { VQBModule, type VQBActions } from '@/store';
/*
StepComponent to use in QueryBuilder context, it uses pinia store to handle selectedColumns
*/
@Component({
name: 'store-step-from-component',
})
export default class StoreStepFormComponent extends Vue {
@Prop({ type: String, required: true })
name!: PipelineStepName;
@Prop({ type: Object, default: undefined })
initialStepValue?: Record<string, any>;
@Prop({ type: Object, default: undefined })
stepFormDefaults!: Record<string, any>;
@Prop({ type: String, default: undefined })
backendError?: string;
@Action(VQBModule) selectStep!: VQBActions['selectStep'];
@Action(VQBModule) setSelectedColumns!: VQBActions['setSelectedColumns'];
@Action(VQBModule) getColumnNamesFromPipeline!: VQBActions['getColumnNamesFromPipeline'];
@State(VQBModule) interpolateFunc!: InterpolateFunction;
@State(VQBModule) selectedStepIndex!: number;
@State(VQBModule) variables!: ScopeContext;
@Getter(VQBModule) translator!: string;
@Getter(VQBModule) computedActiveStepIndex!: number;
@State(VQBModule) selectedColumns!: string[];
@State(VQBModule) availableDomains!: { name: string; uid: string }[];
@State(VQBModule) unjoinableDomains!: { name: string; uid: string }[];
@Getter(VQBModule) columnTypes!: ColumnTypeMapping;
@State(VQBModule) availableVariables?: VariablesBucket;
@State(VQBModule) variableDelimiters?: VariableDelimiters;
@State(VQBModule) trustedVariableDelimiters?: VariableDelimiters;
get isStepCreation() {
return this.initialStepValue === undefined;
}
get formComponent() {
return StepFormsComponents[this.name];
}
back() {
this.$emit('back');
const idx = this.isStepCreation ? this.computedActiveStepIndex : this.selectedStepIndex + 1;
this.selectStep({ index: idx });
}
formSaved(step: PipelineStep) {
this.$emit('formSaved', step);
}
}
</script>
107 changes: 107 additions & 0 deletions ui/stories/StepForm.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { action } from '@storybook/addon-actions';
import type { Meta, StoryObj } from '@storybook/vue';

import StepFormComponent from '@/components/stepforms/StepFormComponent.vue';
import { STEP_LABELS } from '@/types';

const RELATIVE_SAMPLE_VARIABLES = [
{
label: 'Today',
identifier: 'today',
value: new Date('1/12/2021'),
},
{
label: 'Last month',
identifier: 'last_month',
value: new Date('11/12/2021'),
},
{
label: 'Last year',
identifier: 'last_year',

value: new Date('1/12/2020'),
},
];

const SAMPLE_VARIABLES = [
{
identifier: 'dates.last_7_days',
trusted: true,
label: 'Last 7 days',
},
{
identifier: 'dates.last_14_days',
trusted: true,
label: 'Last 14 days',
},
{
identifier: 'dates.last_30_days',
trusted: true,
label: 'Last 30 days',
},
{
identifier: 'dates.last_3_months',
trusted: true,
label: 'Last 3 Months',
},
{
identifier: 'dates.last_12_months',
trusted: true,
label: 'Last 12 Months',
},
{
identifier: 'dates.month_to_date',
trusted: true,
label: 'Month to date',
},
{
identifier: 'dates.quarter_to_date',
trusted: true,
label: 'Quarter to date',
},
{
identifier: 'dates.all_time',
trusted: true,
label: 'All time',
},
...RELATIVE_SAMPLE_VARIABLES,
];

const VARIABLES = SAMPLE_VARIABLES.reduce((vars, item) => {
vars[item.identifier] = 'value' in item ? item.value : item.identifier;
return vars;
}, {} as Record<string, any>);

export default {
component: StepFormComponent,
} as Meta<StepFormComponent>;

export const Default: StoryObj<StepFormComponent> = {
render: (args, { argTypes }) => ({
components: { StepFormComponent },
props: Object.keys(argTypes),
template: '<StepFormComponent v-bind="$props" @formSaved="onFormSaved" @back="onBack" />',
methods: {
onFormSaved: action('formSaved'),
onBack: action('back'),
},
}),
args: {
name: 'text',
availableDomains: [{ name: 'other_domain', uid: 'other_domain' }],
unjoinableDomains: [],
columnTypes: { a: 'string', b: 'boolean' },
availableVariables: SAMPLE_VARIABLES,
variableDelimiters: { start: '<%=', end: '%>' },
trustedVariableDelimiters: { start: '{{', end: '}}' },
variables: VARIABLES,
interpolateFunc: (a) => a,
getColumnNamesFromPipeline: () => Promise.resolve(['c', 'd']),
},
argTypes: {
name: {
options: Object.keys(STEP_LABELS),
control: { type: 'select' },
},
},
};
2 changes: 1 addition & 1 deletion ui/tests/unit/filter-simple-condition-widget.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Wrapper } from '@vue/test-utils';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import { describe, expect, it, vi } from 'vitest';
import { describe, expect, it } from 'vitest';

import AutocompleteWidget from '@/components/stepforms/widgets/Autocomplete.vue';
import FilterSimpleConditionWidget from '@/components/stepforms/widgets/FilterSimpleCondition.vue';
Expand Down
Loading

0 comments on commit dd9233d

Please sign in to comment.