feat ui: ai widgets (#2043)
This commit is contained in:
parent
37276006fb
commit
8003de2627
7 changed files with 162 additions and 15 deletions
|
|
@ -22,10 +22,11 @@ import {
|
|||
USER_PATH,
|
||||
RETENTION
|
||||
} from 'App/constants/card';
|
||||
import { eventKeys, filtersMap } from 'App/types/filter/newFilter';
|
||||
import { eventKeys } from 'App/types/filter/newFilter';
|
||||
import { renderClickmapThumbnail } from './renderMap';
|
||||
import Widget from 'App/mstore/types/widget';
|
||||
import FilterItem from 'Shared/Filters/FilterItem';
|
||||
import { Input } from 'antd'
|
||||
|
||||
interface Props {
|
||||
history: any;
|
||||
|
|
@ -40,7 +41,8 @@ function WidgetForm(props: Props) {
|
|||
params: { siteId, dashboardId }
|
||||
}
|
||||
} = props;
|
||||
const { metricStore, dashboardStore } = useStore();
|
||||
const [aiQuery, setAiQuery] = useState('')
|
||||
const { metricStore, dashboardStore, aiFiltersStore } = useStore();
|
||||
const isSaving = metricStore.isSaving;
|
||||
const metric: any = metricStore.instance;
|
||||
const [initialInstance, setInitialInstance] = useState();
|
||||
|
|
@ -54,7 +56,7 @@ function WidgetForm(props: Props) {
|
|||
const isPathAnalysis = metric.metricType === USER_PATH;
|
||||
const isRetention = metric.metricType === RETENTION;
|
||||
const canAddSeries = metric.series.length < 3;
|
||||
const eventsLength = metric.series[0].filter.filters.filter((i: any) => i.isEvent).length;
|
||||
const eventsLength = metric.series[0].filter.filters.filter((i: any) => i && i.isEvent).length;
|
||||
const cannotSaveFunnel = isFunnel && (!metric.series[0] || eventsLength <= 1);
|
||||
|
||||
const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes(
|
||||
|
|
@ -103,7 +105,7 @@ function WidgetForm(props: Props) {
|
|||
history.replace(
|
||||
withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId)
|
||||
);
|
||||
dashboardStore.addWidgetToDashboard(
|
||||
void dashboardStore.addWidgetToDashboard(
|
||||
dashboardStore.getDashboard(parseInt(dashboardId, 10))!,
|
||||
[savedMetric.metricId]
|
||||
);
|
||||
|
|
@ -130,6 +132,20 @@ function WidgetForm(props: Props) {
|
|||
metricStore.merge(w.fromJson(initialInstance), false);
|
||||
};
|
||||
|
||||
const fetchResults = () => {
|
||||
aiFiltersStore.getCardFilters(aiQuery, metric.metricType)
|
||||
.then((f) => {
|
||||
metric.createSeries(f.filters);
|
||||
})
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: any) => {
|
||||
if (event.key === 'Enter') {
|
||||
fetchResults();
|
||||
}
|
||||
};
|
||||
|
||||
const testingKey = localStorage.getItem('__mauricio_testing_access') === 'true';
|
||||
return (
|
||||
<div className='p-6'>
|
||||
<div className='form-group'>
|
||||
|
|
@ -237,7 +253,20 @@ function WidgetForm(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testingKey ? <Input
|
||||
placeholder="AI Query"
|
||||
value={aiQuery}
|
||||
onChange={(e: any) => setAiQuery(e.target.value)}
|
||||
className="w-full mb-2"
|
||||
onKeyDown={handleKeyDown}
|
||||
/> : null}
|
||||
{aiFiltersStore.isLoading ? (
|
||||
<div>
|
||||
<div className='flex items-center font-medium py-2'>
|
||||
Loading
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{!isPredefined && (
|
||||
<div>
|
||||
<div className='flex items-center font-medium py-2'>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { makeAutoObservable } from 'mobx';
|
||||
import { aiService } from 'App/services';
|
||||
import Filter from 'Types/filter';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
import { filtersMap } from "Types/filter/newFilter";
|
||||
import { filtersMap } from 'Types/filter/newFilter';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
|
||||
import { aiService } from 'App/services';
|
||||
|
||||
export default class AiFiltersStore {
|
||||
filters: Record<string, any> = { filters: [] };
|
||||
cardFilters: Record<string, any> = { filters: [] };
|
||||
filtersSetKey = 0;
|
||||
isLoading: boolean = false;
|
||||
|
||||
|
|
@ -18,6 +20,56 @@ export default class AiFiltersStore {
|
|||
this.filtersSetKey += 1;
|
||||
};
|
||||
|
||||
setCardFilters = (filters: Record<string, any>): void => {
|
||||
this.cardFilters = filters;
|
||||
this.filtersSetKey += 1;
|
||||
};
|
||||
|
||||
getCardFilters = async (query: string, chartType: string): Promise<any> => {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const r = await aiService.getCardFilters(query, chartType);
|
||||
const filterObj = Filter({
|
||||
filters: r.filters.map((f: Record<string, any>) => {
|
||||
if (f.key === 'fetch') {
|
||||
return mapFetch(f);
|
||||
}
|
||||
if (f.key === 'graphql') {
|
||||
return mapGraphql(f);
|
||||
}
|
||||
|
||||
const matchingFilter = Object.keys(filtersMap).find((k) =>
|
||||
f.key === 'metadata' ? `_${f.source}` === k : f.key === k
|
||||
);
|
||||
|
||||
if (f.key === 'duration') {
|
||||
const filter = matchingFilter
|
||||
? { ...filtersMap[matchingFilter], ...f }
|
||||
: { ...f, value: f.value ?? [] };
|
||||
return {
|
||||
...filter,
|
||||
value: filter.value
|
||||
? filter.value.map((i: string) => parseInt(i, 10) * 60 * 1000)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
return matchingFilter
|
||||
? { ...filtersMap[matchingFilter], ...f }
|
||||
: { ...f, value: f.value ?? [] };
|
||||
}),
|
||||
eventsOrder: r.eventsOrder.toLowerCase(),
|
||||
});
|
||||
|
||||
this.setCardFilters(filterObj);
|
||||
return filterObj.toJS();
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
getSearchFilters = async (query: string): Promise<any> => {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
|
|
@ -28,17 +80,28 @@ export default class AiFiltersStore {
|
|||
return mapFetch(f);
|
||||
}
|
||||
if (f.key === 'graphql') {
|
||||
return mapGraphql(f)
|
||||
return mapGraphql(f);
|
||||
}
|
||||
|
||||
const matchingFilter = Object.keys(filtersMap).find(k => f.key === 'metadata' ? `_${f.source}` === k : f.key === k)
|
||||
const matchingFilter = Object.keys(filtersMap).find((k) =>
|
||||
f.key === 'metadata' ? `_${f.source}` === k : f.key === k
|
||||
);
|
||||
|
||||
if (f.key === 'duration') {
|
||||
const filter = matchingFilter ? { ...filtersMap[matchingFilter], ...f } : { ...f, value: f.value ?? [] };
|
||||
return { ...filter, value: filter.value ? filter.value.map((i: string) => parseInt(i, 10) * 60 * 1000) : null };
|
||||
const filter = matchingFilter
|
||||
? { ...filtersMap[matchingFilter], ...f }
|
||||
: { ...f, value: f.value ?? [] };
|
||||
return {
|
||||
...filter,
|
||||
value: filter.value
|
||||
? filter.value.map((i: string) => parseInt(i, 10) * 60 * 1000)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
return matchingFilter ? { ...filtersMap[matchingFilter], ...f } : { ...f, value: f.value ?? [] };
|
||||
return matchingFilter
|
||||
? { ...filtersMap[matchingFilter], ...f }
|
||||
: { ...f, value: f.value ?? [] };
|
||||
}),
|
||||
eventsOrder: r.eventsOrder.toLowerCase(),
|
||||
});
|
||||
|
|
@ -167,5 +230,5 @@ const mapGraphql = (filter: Record<string, any>) => {
|
|||
return {
|
||||
...defaultGraphqlFilter,
|
||||
filters: updateFilters(defaultGraphqlFilter.filters, filter.filters),
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -76,6 +76,15 @@ export default class Filter {
|
|||
return this
|
||||
}
|
||||
|
||||
fromData(data) {
|
||||
this.name = data.name
|
||||
this.filters = data.filters.map((i: Record<string, any>) =>
|
||||
new FilterItem(undefined, this.isConditional, this.isMobile).fromData(i)
|
||||
)
|
||||
this.eventsOrder = data.eventsOrder
|
||||
return this
|
||||
}
|
||||
|
||||
toJsonDrilldown() {
|
||||
const json = {
|
||||
name: this.name,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,27 @@ export default class FilterItem {
|
|||
});
|
||||
}
|
||||
|
||||
fromData(data: any) {
|
||||
this.type = data.type
|
||||
this.key = data.key
|
||||
this.label = data.label
|
||||
this.operatorOptions = data.operatorOptions
|
||||
this.hasSource = data.hasSource
|
||||
this.category = data.category
|
||||
this.sourceOperatorOptions = data.sourceOperatorOptions
|
||||
this.value = data.value
|
||||
this.isEvent = Boolean(data.isEvent)
|
||||
this.operator = data.operator
|
||||
this.source = data.source
|
||||
this.sourceOperator = data.sourceOperator
|
||||
this.filters = data.filters
|
||||
this.isActive = Boolean(data.isActive)
|
||||
this.completed = data.completed
|
||||
this.dropped = data.dropped
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fromJson(json: any, mainFilterKey = '') {
|
||||
const isMetadata = json.type === FilterKey.METADATA;
|
||||
let _filter: any = (isMetadata ? filtersMap['_' + json.source] : filtersMap[json.type]) || {};
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ export default class FilterSeries {
|
|||
return this
|
||||
}
|
||||
|
||||
fromData(data) {
|
||||
this.seriesId = data.seriesId
|
||||
this.name = data.name
|
||||
this.filter = new Filter().fromData(data.filter)
|
||||
return this
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
seriesId: this.seriesId,
|
||||
|
|
|
|||
|
|
@ -123,12 +123,21 @@ export default class Widget {
|
|||
this.series.splice(index, 1);
|
||||
}
|
||||
|
||||
setSeries(series: FilterSeries[]) {
|
||||
this.series = series;
|
||||
}
|
||||
|
||||
addSeries() {
|
||||
const series = new FilterSeries();
|
||||
series.name = 'Series ' + (this.series.length + 1);
|
||||
this.series.push(series);
|
||||
}
|
||||
|
||||
createSeries(filters: Record<string, any>) {
|
||||
const series = new FilterSeries().fromData({ filter: { filters } , name: 'AI Query', seriesId: 1 })
|
||||
this.setSeries([series])
|
||||
}
|
||||
|
||||
fromJson(json: any, period?: any) {
|
||||
json.config = json.config || {};
|
||||
runInAction(() => {
|
||||
|
|
|
|||
|
|
@ -31,4 +31,13 @@ export default class AiService extends BaseService {
|
|||
const { data } = await r.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
async getCardFilters(query: string, chartType: string): Promise<Record<string, any>> {
|
||||
const r = await this.client.post('/intelligent/search-plus', {
|
||||
question: query,
|
||||
chartType
|
||||
});
|
||||
const { data } = await r.json();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue