Skip to content

Commit

Permalink
feat ui: ai widgets (openreplay#2043)
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-delirium authored Apr 5, 2024
1 parent 3727600 commit 8003de2
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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(
Expand Down Expand Up @@ -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]
);
Expand All @@ -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'>
Expand Down Expand Up @@ -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'>
Expand Down
83 changes: 73 additions & 10 deletions frontend/app/mstore/aiFiltersStore.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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 {
Expand All @@ -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(),
});
Expand Down Expand Up @@ -167,5 +230,5 @@ const mapGraphql = (filter: Record<string, any>) => {
return {
...defaultGraphqlFilter,
filters: updateFilters(defaultGraphqlFilter.filters, filter.filters),
}
}
};
};
9 changes: 9 additions & 0 deletions frontend/app/mstore/types/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions frontend/app/mstore/types/filterItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]) || {};
Expand Down
7 changes: 7 additions & 0 deletions frontend/app/mstore/types/filterSeries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions frontend/app/mstore/types/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
9 changes: 9 additions & 0 deletions frontend/app/services/AiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

0 comments on commit 8003de2

Please sign in to comment.