diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
index 3e046e52f5..35ebd62af5 100644
--- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
@@ -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 (
@@ -237,7 +253,20 @@ function WidgetForm(props: Props) {
)}
-
+ {testingKey ? setAiQuery(e.target.value)}
+ className="w-full mb-2"
+ onKeyDown={handleKeyDown}
+ /> : null}
+ {aiFiltersStore.isLoading ? (
+
+ ) : null}
{!isPredefined && (
diff --git a/frontend/app/mstore/aiFiltersStore.ts b/frontend/app/mstore/aiFiltersStore.ts
index ce0b75abc4..cfd5e69fe4 100644
--- a/frontend/app/mstore/aiFiltersStore.ts
+++ b/frontend/app/mstore/aiFiltersStore.ts
@@ -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
= { filters: [] };
+ cardFilters: Record = { filters: [] };
filtersSetKey = 0;
isLoading: boolean = false;
@@ -18,6 +20,56 @@ export default class AiFiltersStore {
this.filtersSetKey += 1;
};
+ setCardFilters = (filters: Record): void => {
+ this.cardFilters = filters;
+ this.filtersSetKey += 1;
+ };
+
+ getCardFilters = async (query: string, chartType: string): Promise => {
+ this.isLoading = true;
+ try {
+ const r = await aiService.getCardFilters(query, chartType);
+ const filterObj = Filter({
+ filters: r.filters.map((f: Record) => {
+ 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 => {
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) => {
return {
...defaultGraphqlFilter,
filters: updateFilters(defaultGraphqlFilter.filters, filter.filters),
- }
-}
+ };
+};
diff --git a/frontend/app/mstore/types/filter.ts b/frontend/app/mstore/types/filter.ts
index 6f6e5dba97..8b77e4f5f5 100644
--- a/frontend/app/mstore/types/filter.ts
+++ b/frontend/app/mstore/types/filter.ts
@@ -76,6 +76,15 @@ export default class Filter {
return this
}
+ fromData(data) {
+ this.name = data.name
+ this.filters = data.filters.map((i: Record) =>
+ new FilterItem(undefined, this.isConditional, this.isMobile).fromData(i)
+ )
+ this.eventsOrder = data.eventsOrder
+ return this
+ }
+
toJsonDrilldown() {
const json = {
name: this.name,
diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts
index 354790f45a..19426ecea0 100644
--- a/frontend/app/mstore/types/filterItem.ts
+++ b/frontend/app/mstore/types/filterItem.ts
@@ -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]) || {};
diff --git a/frontend/app/mstore/types/filterSeries.ts b/frontend/app/mstore/types/filterSeries.ts
index 46d6f64726..6a42d10b9c 100644
--- a/frontend/app/mstore/types/filterSeries.ts
+++ b/frontend/app/mstore/types/filterSeries.ts
@@ -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,
diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts
index 3cd2c22416..511794cfa0 100644
--- a/frontend/app/mstore/types/widget.ts
+++ b/frontend/app/mstore/types/widget.ts
@@ -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) {
+ 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(() => {
diff --git a/frontend/app/services/AiService.ts b/frontend/app/services/AiService.ts
index ad1cef8591..85feef6772 100644
--- a/frontend/app/services/AiService.ts
+++ b/frontend/app/services/AiService.ts
@@ -31,4 +31,13 @@ export default class AiService extends BaseService {
const { data } = await r.json();
return data;
}
+
+ async getCardFilters(query: string, chartType: string): Promise> {
+ const r = await this.client.post('/intelligent/search-plus', {
+ question: query,
+ chartType
+ });
+ const { data } = await r.json();
+ return data;
+ }
}