feat ui: ai widgets (#2043)

This commit is contained in:
Delirium 2024-04-05 15:22:22 +02:00 committed by GitHub
parent 37276006fb
commit 8003de2627
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 162 additions and 15 deletions

View file

@ -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'>

View file

@ -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),
}
}
};
};

View file

@ -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,

View file

@ -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]) || {};

View file

@ -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,

View file

@ -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(() => {

View file

@ -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;
}
}