diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx index 8fd11d04f..f27147280 100644 --- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -41,9 +41,9 @@ function MetricViewHeader() { // Show header if there are cards or if a filter is active const showHeader = cardsLength > 0 || isFilterActive; - useEffect(() => { - metricStore.updateKey('sort', { by: 'desc' }); - }, [metricStore]); + // useEffect(() => { + // metricStore.updateKey('sort', { by: 'desc' }); + // }, [metricStore]); const handleMenuClick = ({ key }: { key: string }) => { metricStore.updateKey('filter', { ...filter, type: key }); diff --git a/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx index 7af94cbad..061a2c94f 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx @@ -8,13 +8,13 @@ import { Button, Dropdown, Modal as AntdModal, - Avatar, + Avatar, TableColumnType } from 'antd'; import { TeamOutlined, LockOutlined, EditOutlined, - DeleteOutlined, + DeleteOutlined } from '@ant-design/icons'; import { EllipsisVertical } from 'lucide-react'; import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface'; @@ -37,90 +37,41 @@ interface Props { toggleSelection?: (metricId: number | number[]) => void; disableSelection?: boolean; inLibrary?: boolean; + loading?: boolean; } const ListView: React.FC = ({ - list, - siteId, - selectedList, - toggleSelection, - disableSelection = false, - inLibrary = false -}) => { + list, + siteId, + selectedList, + toggleSelection, + disableSelection = false, + inLibrary = false, + loading = false + }) => { const { t } = useTranslation(); - const [sorter, setSorter] = useState<{ field: string; order: 'ascend' | 'descend' }>({ - field: 'lastModified', - order: 'descend', - }); - const [pagination, setPagination] = useState({ - current: 1, - pageSize: 10, - }); const [editingMetricId, setEditingMetricId] = useState(null); const [newName, setNewName] = useState(''); const { metricStore } = useStore(); const history = useHistory(); - const sortedData = useMemo( - () => - [...list].sort((a, b) => { - if (sorter.field === 'lastModified') { - return sorter.order === 'ascend' - ? new Date(a.lastModified).getTime() - - new Date(b.lastModified).getTime() - : new Date(b.lastModified).getTime() - - new Date(a.lastModified).getTime(); - } - if (sorter.field === 'name') { - return sorter.order === 'ascend' - ? a.name?.localeCompare(b.name) || 0 - : b.name?.localeCompare(a.name) || 0; - } - if (sorter.field === 'owner') { - return sorter.order === 'ascend' - ? a.owner?.localeCompare(b.owner) || 0 - : b.owner?.localeCompare(a.owner) || 0; - } - return 0; - }), - [list, sorter], - ); - - const paginatedData = useMemo(() => { - const start = ((pagination.current || 1) - 1) * (pagination.pageSize || 10); - return sortedData.slice(start, start + (pagination.pageSize || 10)); - }, [sortedData, pagination]); - const totalMessage = ( <> {t('Showing')}{' '} - {(pagination.pageSize || 10) * ((pagination.current || 1) - 1) + 1} + {(metricStore.pageSize || 10) * ((metricStore.page || 1) - 1) + 1} {' '} {t('to')}{' '} {Math.min( - (pagination.pageSize || 10) * (pagination.current || 1), - list.length, + (metricStore.pageSize || 10) * (metricStore.page || 1), + list.length )} {' '} {t('of')} {list.length} {t('cards')} ); - const handleTableChange = ( - pag: TablePaginationConfig, - _filters: Record, - sorterParam: SorterResult | SorterResult[], - ) => { - const sortRes = sorterParam as SorterResult; - setSorter({ - field: sortRes.field as string, - order: sortRes.order as 'ascend' | 'descend', - }); - setPagination(pag); - }; - const parseDate = (dateString: string) => { let date = new Date(dateString); if (isNaN(date.getTime())) { @@ -182,7 +133,7 @@ const ListView: React.FC = ({ cancelText: t('No'), onOk: async () => { await metricStore.delete(metric); - }, + } }); } if (key === 'rename') { @@ -206,7 +157,7 @@ const ListView: React.FC = ({ const menuItems = [ { key: 'rename', icon: , label: t('Rename') }, - { key: 'delete', icon: , label: t('Delete') }, + { key: 'delete', icon: , label: t('Delete') } ]; const renderTitle = (_text: string, metric: Widget) => ( @@ -245,80 +196,109 @@ const ListView: React.FC = ({ ); - const columns = [ + const columns: TableColumnType[] = [ { title: t('Title'), dataIndex: 'name', key: 'title', className: 'cap-first pl-4', sorter: true, + sortOrder: metricStore.sort.field === 'name' ? metricStore.sort.order : undefined, width: inLibrary ? '31%' : '25%', - render: renderTitle, + render: renderTitle }, { title: t('Owner'), - dataIndex: 'owner', + dataIndex: 'owner_email', key: 'owner', className: 'capitalize', sorter: true, + sortOrder: metricStore.sort.field === 'owner_email' ? metricStore.sort.order : undefined, width: inLibrary ? '31%' : '25%', - render: renderOwner, + render: renderOwner }, { title: t('Last Modified'), - dataIndex: 'lastModified', + dataIndex: 'edited_at', key: 'lastModified', sorter: true, + sortOrder: metricStore.sort.field === 'edited_at' ? metricStore.sort.order : undefined, width: inLibrary ? '31%' : '25%', - render: renderLastModified, - }, + render: renderLastModified + } ]; + if (!inLibrary) { columns.push({ title: '', key: 'options', className: 'text-right', width: '5%', - render: renderOptions, + render: renderOptions }); } + // if (metricStore.sort.field) { + // columns.forEach((col) => { + // col.sortOrder = col.key === metricStore.sort.field ? metricStore.sort.order : false; + // }); + // } + + console.log('store', metricStore.sort); + + const handleTableChange = ( + pag: TablePaginationConfig, + _filters: Record, + sorterParam: SorterResult | SorterResult[] + ) => { + const sorter = Array.isArray(sorterParam) ? sorterParam[0] : sorterParam; + let order = sorter.order; + if (metricStore.sort.field === sorter.field) { + order = metricStore.sort.order === 'ascend' ? 'descend' : 'ascend'; + } + console.log('sorter', { field: sorter.field, order }); + metricStore.updateKey('sort', { field: sorter.field, order }); + metricStore.updateKey('page', pag.current || 1); + }; + return ( <> ({ - onClick: () => { - if (!disableSelection) toggleSelection?.(record?.metricId); - }, - }) + onClick: () => { + if (!disableSelection) toggleSelection?.(record?.metricId); + } + }) : undefined } rowSelection={ !disableSelection ? { - selectedRowKeys: selectedList, - onChange: (keys) => toggleSelection && toggleSelection(keys), - columnWidth: 16, - } + selectedRowKeys: selectedList, + onChange: (keys) => toggleSelection && toggleSelection(keys), + columnWidth: 16 + } : undefined } pagination={{ - current: pagination.current, - pageSize: pagination.pageSize, - total: sortedData.length, + current: metricStore.page, + pageSize: metricStore.pageSize, + total: metricStore.total, showSizeChanger: false, className: 'px-4', showLessItems: true, showTotal: () => totalMessage, size: 'small', - simple: true, + simple: true }} /> void; inLibrary?: boolean; @@ -23,28 +22,27 @@ function MetricsList({ const { t } = useTranslation(); const { metricStore, dashboardStore } = useStore(); const metricsSearch = metricStore.filter.query; - const listView = inLibrary ? true : metricStore.listView; const [selectedMetrics, setSelectedMetrics] = useState([]); const dashboard = dashboardStore.selectedDashboard; const existingCardIds = useMemo( () => dashboard?.widgets?.map((i) => parseInt(i.metricId)), - [dashboard], + [dashboard] ); const cards = useMemo( () => onSelectionChange ? metricStore.filteredCards.filter( - (i) => !existingCardIds?.includes(parseInt(i.metricId)), - ) + (i) => !existingCardIds?.includes(parseInt(i.metricId)) + ) : metricStore.filteredCards, - [metricStore.filteredCards, existingCardIds, onSelectionChange], + [metricStore.filteredCards, existingCardIds, onSelectionChange] ); const loading = metricStore.isLoading; useEffect(() => { void metricStore.fetchList(); - }, [metricStore]); + }, [metricStore.page, metricStore.filter, metricStore.sort]); useEffect(() => { if (!onSelectionChange) return; @@ -69,14 +67,8 @@ function MetricsList({ metricStore.updateKey('sessionsPage', 1); }, [metricStore]); - const showOwn = metricStore.filter.showMine; - const toggleOwn = () => { - metricStore.updateKey('showMine', !showOwn); - }; - const isFiltered = - metricsSearch !== '' || - (metricStore.filter.type && metricStore.filter.type !== 'all'); + const isFiltered = metricStore.filter.query !== '' || metricStore.filter.type !== 'all'; const searchImageDimensions = { width: 60, height: 'auto' }; const defaultImageDimensions = { width: 600, height: 'auto' }; @@ -86,101 +78,65 @@ function MetricsList({ : defaultImageDimensions; return ( - - - -
- {isFiltered - ? t('No matching results') - : t('Unlock insights with data cards')} -
+ + +
+ {isFiltered + ? t('No matching results') + : t('Unlock insights with data cards')}
- } - subtext={ - isFiltered ? ( - '' - ) : ( -
-
- {t('Create and customize cards to analyze trends and user behavior effectively.')} -
- } - trigger="click" - > - - -
- ) - } - > - {listView ? ( - - setSelectedMetrics( - checked - ? cards - .map((i: any) => i.metricId) - .slice(0, 30 - (existingCardIds?.length || 0)) - : [], - ) - } - /> + + } + subtext={ + isFiltered ? ( + '' ) : ( - <> - -
-
- {t('Showing')}{' '} - - {Math.min(cards.length, metricStore.pageSize)} - {' '} - {t('out of')}  - {cards.length}  - {t('cards')} -
- metricStore.updateKey('page', page)} - limit={metricStore.pageSize} - debounceRequest={100} - /> +
+
+ {t('Create and customize cards to analyze trends and user behavior effectively.')}
- - )} - - + } + trigger="click" + > + + +
+ ) + } + > + + // setSelectedMetrics( + // checked + // ? cards + // .map((i: any) => i.metricId) + // .slice(0, 30 - (existingCardIds?.length || 0)) + // : [] + // ) + // } + /> + ); } diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 75a048238..42abbeab8 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -10,7 +10,7 @@ import { HEATMAP, USER_PATH, RETENTION, - CATEGORIES, + CATEGORIES } from 'App/constants/card'; import { clickmapFilter } from 'App/types/filter/newFilter'; import { getRE } from 'App/utils'; @@ -31,7 +31,7 @@ const handleFilter = (card: Widget, filterType?: string) => { FilterKey.ERRORS, FilterKey.FETCH, `${TIMESERIES}_4xx_requests`, - `${TIMESERIES}_slow_network_requests`, + `${TIMESERIES}_slow_network_requests` ].includes(metricOf); } if (filterType === CATEGORIES.web_analytics) { @@ -41,7 +41,7 @@ const handleFilter = (card: Widget, filterType?: string) => { FilterKey.REFERRER, FilterKey.USERID, FilterKey.LOCATION, - FilterKey.USER_DEVICE, + FilterKey.USER_DEVICE ].includes(metricOf); } } else { @@ -75,58 +75,42 @@ interface MetricFilter { query?: string; showMine?: boolean; type?: string; - dashboard?: []; + // dashboard?: []; } + export default class MetricStore { isLoading: boolean = false; - isSaving: boolean = false; - metrics: Widget[] = []; - instance = new Widget(); - page: number = 1; - + total: number = 0; pageSize: number = 10; - metricsSearch: string = ''; - - sort: any = { by: 'desc' }; - - filter: MetricFilter = { type: 'all', dashboard: [], query: '' }; - + sort: any = { columnKey: '', field: '', order: false }; + filter: any = { type: '', query: '' }; sessionsPage: number = 1; - sessionsPageSize: number = 10; - listView?: boolean = true; - clickMapFilter: boolean = false; - clickMapSearch = ''; - clickMapLabel = ''; - cardCategory: string | null = CATEGORIES.product_analytics; - focusedSeriesName: string | null = null; - disabledSeries: string[] = []; - drillDown = false; constructor() { makeAutoObservable(this); } - get sortedWidgets() { - return [...this.metrics].sort((a, b) => - this.sort.by === 'desc' - ? b.lastModified - a.lastModified - : a.lastModified - b.lastModified, - ); - } + // get sortedWidgets() { + // return [...this.metrics].sort((a, b) => + // this.sort.by === 'desc' + // ? b.lastModified - a.lastModified + // : a.lastModified - b.lastModified + // ); + // } get filteredCards() { const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null; @@ -138,7 +122,7 @@ export default class MetricStore { (card) => (this.filter.showMine ? card.owner === - JSON.parse(localStorage.getItem('user')!).account.email + JSON.parse(localStorage.getItem('user')!).account.email : true) && handleFilter(card, this.filter.type) && (!dbIds.length || @@ -147,13 +131,13 @@ export default class MetricStore { .some((id) => dbIds.includes(id))) && // @ts-ignore (!filterRE || - ['name', 'owner'].some((key) => filterRE.test(card[key]))), - ) - .sort((a, b) => - this.sort.by === 'desc' - ? b.lastModified - a.lastModified - : a.lastModified - b.lastModified, + ['name', 'owner'].some((key) => filterRE.test(card[key]))) ); + // .sort((a, b) => + // this.sort.by === 'desc' + // ? b.lastModified - a.lastModified + // : a.lastModified - b.lastModified + // ); } // State Actions @@ -182,6 +166,7 @@ export default class MetricStore { } updateKey(key: string, value: any) { + console.log('key', key, value); // @ts-ignore this[key] = value; @@ -207,7 +192,7 @@ export default class MetricStore { this.instance.series[i].filter.eventsOrderSupport = [ 'then', 'or', - 'and', + 'and' ]; }); if (type === HEATMAP && 'series' in obj) { @@ -254,7 +239,7 @@ export default class MetricStore { namesMap: {}, avg: 0, percentiles: [], - values: [], + values: [] }; const obj: any = { metricType: value, data: defaultData }; obj.series = this.instance.series; @@ -311,7 +296,7 @@ export default class MetricStore { if (obj.series[0] && obj.series[0].filter.filters.length < 1) { obj.series[0].filter.addFilter({ ...clickmapFilter, - value: [''], + value: [''] }); } } @@ -341,7 +326,7 @@ export default class MetricStore { updateInList(metric: Widget) { // @ts-ignore const index = this.metrics.findIndex( - (m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY], + (m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY] ); if (index >= 0) { this.metrics[index] = metric; @@ -358,12 +343,6 @@ export default class MetricStore { this.metrics = this.metrics.filter((m) => m[Widget.ID_KEY] !== id); } - get paginatedList(): Widget[] { - const start = (this.page - 1) * this.pageSize; - const end = start + this.pageSize; - return this.metrics.slice(start, end); - } - // API Communication async save(metric: Widget): Promise { this.isSaving = true; @@ -396,16 +375,27 @@ export default class MetricStore { this.metrics = metrics; } - fetchList() { + async fetchList() { this.setLoading(true); - return metricService - .getMetrics() - .then((metrics: any[]) => { - this.setMetrics(metrics.map((m) => new Widget().fromJson(m))); - }) - .finally(() => { - this.setLoading(false); - }); + try { + const resp = await metricService + .getMetricsPaginated({ + page: this.page, + limit: this.pageSize, + sort: { + field: this.sort.field, + order: this.sort.order === 'ascend' ? 'asc' : 'desc' + }, + filter: { + query: this.filter.query, + type: this.filter.type === 'all' ? '' : this.filter.type, + } + }); + this.total = resp.total; + this.setMetrics(resp.list.map((m) => new Widget().fromJson(m))); + } finally { + this.setLoading(false); + } } fetch(id: string, period?: any) { diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index a6c19c050..9bbb0b7f9 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -24,6 +24,17 @@ export default class MetricService { .then((response: { data: any }) => response.data || []); } + /** + * Get all metrics paginated. + * @returns {Promise} + */ + getMetricsPaginated(params: any): Promise { + return this.client + .post('/cards/search', params) + .then((response: { json: () => any }) => response.json()) + .then((response: { data: any }) => response.data || []); + } + /** * Get a metric by metricId. * @param metricId