diff --git a/app/App.tsx b/app/App.tsx index 1eabea22..8bca47b2 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -101,6 +101,7 @@ const App: React.FC = ({ ssrTheme, ssrAuthenticated }) => { !_endsWith(pathname, '/subscribers/invite') && !_endsWith(pathname, '/subscribers/invite') && !_includes(pathname, '/alerts/') && + !_includes(pathname, '/uptime/') && !_includes(pathname, '/settings/') const routesWithOutHeader = [ diff --git a/app/api/index.ts b/app/api/index.ts index c2a533dd..54dec9a8 100644 --- a/app/api/index.ts +++ b/app/api/index.ts @@ -11,7 +11,7 @@ import { authActions } from 'redux/reducers/auth' import sagaActions from 'redux/sagas/actions' import { getAccessToken, removeAccessToken, setAccessToken } from 'utils/accessToken' import { getRefreshToken, removeRefreshToken } from 'utils/refreshToken' -import { DEFAULT_ALERTS_TAKE, API_URL } from 'redux/constants' +import { DEFAULT_ALERTS_TAKE, API_URL, DEFAULT_MONITORS_TAKE } from 'redux/constants' import { IUser } from 'redux/models/IUser' import { IAuth } from 'redux/models/IAuth' import { IProject, IOverall, IProjectNames } from 'redux/models/IProject' @@ -19,6 +19,7 @@ import { IAlerts } from 'redux/models/IAlerts' import { ISharedProject } from 'redux/models/ISharedProject' import { ISubscribers } from 'redux/models/ISubscribers' import { IFilter, IProjectViewCustomEvent } from 'pages/Project/View/interfaces/traffic' +import { Monitor, MonitorOverall } from 'redux/models/Uptime' const debug = Debug('swetrix:api') @@ -984,6 +985,120 @@ export const deleteAlert = (id: string) => throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message }) +export interface ICreateMonitor extends Omit {} + +export const getAllMonitors = (take: number = DEFAULT_MONITORS_TAKE, skip: number = 0) => + api + .get(`project/monitors?take=${take}&skip=${skip}`) + .then( + ( + response, + ): { + results: Monitor[] + total: number + page_total: number + } => response.data, + ) + .catch((error) => { + debug('%s', error) + throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message + }) + +export const getProjectMonitors = (projectId: string, take: number = DEFAULT_MONITORS_TAKE, skip: number = 0) => + api + .get(`project/${projectId}/monitors?take=${take}&skip=${skip}`) + .then( + ( + response, + ): { + results: Monitor[] + total: number + page_total: number + } => response.data, + ) + .catch((error) => { + debug('%s', error) + throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message + }) + +export const getMonitorOverallStats = ( + pid: string, + monitorIds: string[], + period: string, + from = '', + to = '', + timezone = 'Etc/GMT', + password?: string, +) => + api + .get( + `log/monitor-data/birdseye?pid=${pid}&monitorIds=[${_map(monitorIds, (pid) => `"${pid}"`).join( + ',', + )}]&period=${period}&from=${from}&to=${to}&timezone=${timezone}`, + { + headers: { + 'x-password': password, + }, + }, + ) + .then((response): MonitorOverall => response.data) + .catch((error) => { + debug('%s', error) + throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message + }) + +export const getMonitorStats = ( + pid: string, + monitorId: string, + period: string = '1d', + timeBucket: string = 'hour', + from: string = '', + to: string = '', + timezone: string = '', + password: string | undefined = '', +) => + api + .get( + `log/monitor-data?pid=${pid}&monitorId=${monitorId}&timeBucket=${timeBucket}&period=${period}&from=${from}&to=${to}&timezone=${timezone}`, + { + headers: { + 'x-password': password, + }, + }, + ) + .then((response) => response.data) + .catch((error) => { + debug('%s', error) + throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message + }) + +export const createMonitor = (pid: string, data: ICreateMonitor) => + api + .post(`project/${pid}/monitor`, data) + .then((response): Monitor => response.data) + .catch((error) => { + debug('%s', error) + throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message + }) + +export const updateMonitor = (pid: string, id: string, data: Partial) => + api + .patch(`project/${pid}/monitor/${id}`, data) + .then((response): Monitor => response.data) + .catch((error) => { + debug('%s', error) + throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message + }) + +export const deleteMonitor = (pid: string, id: string) => + api + .delete(`project/${pid}/monitor/${id}`) + .then((response) => response.data) + .catch((error) => { + debug('%s', error) + throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message + }) + export const reGenerateCaptchaSecretKey = (pid: string) => api .post(`project/secret-gen/${pid}`) diff --git a/app/hooks/useRequiredParams.ts b/app/hooks/useRequiredParams.ts new file mode 100644 index 00000000..fcc87234 --- /dev/null +++ b/app/hooks/useRequiredParams.ts @@ -0,0 +1,3 @@ +import { useParams } from '@remix-run/react' + +export const useRequiredParams = >() => useParams() as T diff --git a/app/hooks/useSize.ts b/app/hooks/useSize.ts index 9645f777..aa41cb58 100644 --- a/app/hooks/useSize.ts +++ b/app/hooks/useSize.ts @@ -1,8 +1,9 @@ /* eslint-disable consistent-return */ +import type { MutableRefObject } from 'react' import { useEffect, useRef, useState } from 'react' import _get from 'lodash/get' -export default () => { +const useSize = (): [MutableRefObject, { width: number; height: number }] => { const [size, setSize] = useState({ width: 0, height: 0, @@ -28,3 +29,5 @@ export default () => { return [ref, size] } + +export default useSize diff --git a/app/pages/Project/Alerts/View/ProjectAlertsView.tsx b/app/pages/Project/Alerts/View/ProjectAlertsView.tsx index adb7fe96..9facec9e 100644 --- a/app/pages/Project/Alerts/View/ProjectAlertsView.tsx +++ b/app/pages/Project/Alerts/View/ProjectAlertsView.tsx @@ -189,7 +189,6 @@ const ProjectAlerts = ({ projectId }: IProjectAlerts): JSX.Element => { const [isPaidFeatureOpened, setIsPaidFeatureOpened] = useState(false) const navigate = useNavigate() - // @ts-ignore const limits = PLAN_LIMITS[user?.planCode] || PLAN_LIMITS.trial const isLimitReached = authenticated && total >= limits?.maxAlerts diff --git a/app/pages/Project/View/ViewProject.helpers.tsx b/app/pages/Project/View/ViewProject.helpers.tsx index 0456bf1c..a5f1a7e0 100644 --- a/app/pages/Project/View/ViewProject.helpers.tsx +++ b/app/pages/Project/View/ViewProject.helpers.tsx @@ -232,7 +232,8 @@ const getColumns = ( [key: string]: string[] }, ) => { - const { views, bounce, viewsPerUnique, unique, trendlines, sessionDuration, occurrences } = activeChartMetrics + const { views, bounce, viewsPerUnique, unique, trendlines, sessionDuration, occurrences, avgResponseTime } = + activeChartMetrics const columns: any[] = [['x', ..._map(chart.x, (el) => dayjs(el).toDate())]] @@ -255,6 +256,10 @@ const getColumns = ( } } + if (avgResponseTime) { + columns.push(['avgResponseTime', ...chart.avgResponseTime]) + } + if (views) { columns.push(['total', ...chart.visits]) if (trendlines) { @@ -939,7 +944,6 @@ const getSettingsSession = ( } } -// function to get the settings and data for the error details chart const getSettingsError = ( chart: any, timeBucket: string, @@ -1065,7 +1069,6 @@ const getSettingsError = ( } } -// function to get the settings and data for the funnels chart const getSettingsFunnels = (funnel: IAnalyticsFunnel[], totalPageviews: number, t: typeof i18next.t): ChartOptions => { const values = _map(funnel, (step) => { if (_startsWith(step.value, '/')) { @@ -1208,6 +1211,131 @@ const getSettingsFunnels = (funnel: IAnalyticsFunnel[], totalPageviews: number, } } +const getSettingsUptime = ( + chart: any, + timeBucket: string, + timeFormat: string, + rotateXAxis: boolean, + chartType: string, +): ChartOptions => { + const xAxisSize = _size(chart.x) + + const columns = getColumns(chart, { avgResponseTime: true }, {}) + + let regionStart + + if (xAxisSize > 1) { + regionStart = dayjs(chart.x[xAxisSize - 2]).toDate() + } else { + regionStart = dayjs(chart.x[xAxisSize - 1]).toDate() + } + + return { + data: { + x: 'x', + columns, + types: { + avgResponseTime: chartType === chartTypes.line ? area() : bar(), + }, + colors: { + avgResponseTime: '#709775', + }, + regions: { + avgResponseTime: [ + { + // @ts-expect-error + start: regionStart, + style: { + dasharray: '6 2', + }, + }, + ], + }, + }, + transition: { + duration: 500, + }, + resize: { + auto: true, + timer: false, + }, + axis: { + x: { + clipPath: false, + tick: { + fit: true, + rotate: rotateXAxis ? 45 : 0, + // @ts-expect-error + format: + timeFormat === TimeFormat['24-hour'] + ? (x: string) => d3.timeFormat(tbsFormatMapper24h[timeBucket])(x) + : (x: string) => d3.timeFormat(tbsFormatMapper[timeBucket])(x), + }, + localtime: timeFormat === TimeFormat['24-hour'], + type: 'timeseries', + }, + y: { + tick: { + // @ts-expect-error + format: (d: string) => getStringFromTime(getTimeFromSeconds(d), true), + }, + show: true, + inner: true, + }, + }, + tooltip: { + contents: (item: any, _: any, __: any, color: any) => { + return `
    +
  • ${ + timeFormat === TimeFormat['24-hour'] + ? d3.timeFormat(tbsFormatMapperTooltip24h[timeBucket])(item[0].x) + : d3.timeFormat(tbsFormatMapperTooltip[timeBucket])(item[0].x) + }
  • +
    + ${_map( + item, + (el: { id: string; index: number; name: string; value: string; x: Date }) => ` +
  • +
    +
    + ${el.name} +
    + ${getStringFromTime(getTimeFromSeconds(el.value), true)} +
  • + `, + ).join('')}` + }, + }, + point: + chartType === chartTypes.bar + ? {} + : { + focus: { + only: xAxisSize > 1, + }, + pattern: ['circle'], + r: 2, + }, + legend: { + item: { + tile: { + type: 'circle', + width: 10, + r: 3, + }, + }, + hide: ['uniqueCompare', 'totalCompare', 'bounceCompare', 'sessionDurationCompare'], + }, + area: { + linearGradient: true, + }, + bar: { + linearGradient: true, + }, + bindto: '#avgResponseUptimeChart', + } +} + const perfomanceChartCompare = [ 'dnsCompare', 'tlsCompare', @@ -1636,4 +1764,5 @@ export { CHART_MEASURES_MAPPING_PERF, getSettingsError, ERROR_FILTERS_MAPPING, + getSettingsUptime, } diff --git a/app/pages/Project/View/ViewProject.tsx b/app/pages/Project/View/ViewProject.tsx index e358ccef..96932bf5 100644 --- a/app/pages/Project/View/ViewProject.tsx +++ b/app/pages/Project/View/ViewProject.tsx @@ -1,8 +1,19 @@ /* eslint-disable react/no-unstable-nested-components, react/display-name */ -import React, { useState, useEffect, useMemo, memo, useRef, useCallback } from 'react' +import React, { + useState, + useEffect, + useMemo, + memo, + useRef, + useCallback, + createContext, + useContext, + SetStateAction, + Dispatch, +} from 'react' import { ClientOnly } from 'remix-utils/client-only' import useSize from 'hooks/useSize' -import { useNavigate, useParams, Link } from '@remix-run/react' +import { useNavigate, Link } from '@remix-run/react' import bb from 'billboard.js' import { ArrowDownTrayIcon, @@ -21,6 +32,7 @@ import { BookmarkIcon, TrashIcon, PencilIcon, + ClockIcon, } from '@heroicons/react/24/outline' import cx from 'clsx' import dayjs from 'dayjs' @@ -179,9 +191,9 @@ import SearchFilters from './components/SearchFilters' import Filters from './components/Filters' import LiveVisitorsDropdown from './components/LiveVisitorsDropdown' import CountryDropdown from './components/CountryDropdown' -import MetricCards, { MetricCard } from './components/MetricCards' -import PerformanceMetricCards from './components/PerformanceMetricCards' +import { MetricCard, MetricCards, PerformanceMetricCards } from './components/MetricCards' import ProjectAlertsView from '../Alerts/View' +import Uptime from '../uptime/View' import UTMDropdown from './components/UTMDropdown' import TBPeriodSelector from './components/TBPeriodSelector' import { ISession } from './interfaces/session' @@ -211,12 +223,49 @@ import { import { trackCustom } from 'utils/analytics' import AddAViewModal from './components/AddAViewModal' import CustomMetrics from './components/CustomMetrics' +import { useRequiredParams } from 'hooks/useRequiredParams' const SwetrixSDK = require('@swetrix/sdk') const CUSTOM_EV_DROPDOWN_MAX_VISIBLE_LENGTH = 32 const SESSIONS_TAKE = 30 const ERRORS_TAKE = 30 +interface ViewProjectContextType { + // States + projectId: string + projectPassword: string + timezone: string + dateRange: Date[] | null + isLoading: boolean + timeBucket: string + period: string + activePeriod: ITBPeriodPairs | undefined + periodPairs: ITBPeriodPairs[] + timeFormat: '12-hour' | '24-hour' + size: ReturnType[1] + + // Functions + setDateRange: Dispatch> + updatePeriod: (newPeriod: { period: string; label?: string }) => void + updateTimebucket: (newTimebucket: string) => void + setPeriodPairs: Dispatch> + + // Refs + refCalendar: React.MutableRefObject +} + +const ViewProjectContext = createContext(undefined) + +export const useViewProjectContext = () => { + const context = useContext(ViewProjectContext) + + if (context === undefined) { + throw new Error('useViewProjectContext must be used within a ViewProjectContextProvider') + } + + return context +} + interface IViewProject { projects: IProject[] extensions: any @@ -326,13 +375,7 @@ const ViewProject = ({ // dashboardRef is a ref for dashboard div const dashboardRef = useRef(null) - // { id } is a project id from url - // @ts-ignore - const { - id, - }: { - id: string - } = useParams() + const { id } = useRequiredParams<{ id: string }>() // history is a history from react-router-dom const navigate = useNavigate() @@ -346,7 +389,7 @@ const ViewProject = ({ [projects, id, sharedProjects], ) - const projectPassword: string = useMemo( + const projectPassword = useMemo( () => password[id] || (getItem(PROJECTS_PROTECTED)?.[id] as string) || queryPassword || '', [id, password, queryPassword], ) @@ -578,7 +621,7 @@ const ViewProject = ({ try { const funnels = await getFunnels(id, projectPassword) - await updateProject(id, { + updateProject(id, { funnels, }) } catch (reason: any) { @@ -640,7 +683,7 @@ const ViewProject = ({ // @ts-expect-error const timeFormat = useMemo<'12-hour' | '24-hour'>(() => user.timeFormat || TimeFormat['12-hour'], [user]) // ref, size using for logic with responsive chart - const [ref, size] = useSize() as any + const [ref, size] = useSize() // rotateXAxias using for logic with responsive chart const rotateXAxis = useMemo(() => size.width > 0 && size.width < 500, [size]) // customEventsChartData is a data for custom events on a chart @@ -942,7 +985,6 @@ const ViewProject = ({ [t], ) - // tabs is a tabs for project const tabs: { id: string label: string @@ -997,15 +1039,20 @@ const ViewProject = ({ label: t('dashboard.alerts'), icon: BellIcon, }, + ['79eF2Z9rNNvv', 'STEzHcB1rALV'].includes(id) && { + id: PROJECT_TABS.uptime, + label: t('dashboard.uptime'), + icon: ClockIcon, + }, ...adminTabs, - ] + ].filter((x) => !!x) if (projectQueryTabs && projectQueryTabs.length) { return _filter(newTabs, (tab) => _includes(projectQueryTabs, tab.id)) } return newTabs - }, [t, projectQueryTabs, allowedToManage]) + }, [t, id, projectQueryTabs, allowedToManage]) // activeTabLabel is a label for active tab. Using for title in dropdown const activeTabLabel = useMemo(() => _find(tabs, (tab) => tab.id === activeTab)?.label, [tabs, activeTab]) @@ -1625,7 +1672,7 @@ const ViewProject = ({ useEffect(() => { const url = new URL(window.location.toString()) const { searchParams } = url - const tab = searchParams.get('tab') as string + const tab = searchParams.get('tab') as keyof typeof PROJECT_TABS if (PROJECT_TABS[tab]) { setActiveTab(tab) @@ -3627,7 +3674,7 @@ const ViewProject = ({ ) } - if (!project.isDataExists && activeTab !== PROJECT_TABS.errors && !analyticsLoading) { + if (!project.isDataExists && !_includes([PROJECT_TABS.errors, PROJECT_TABS.uptime], activeTab) && !analyticsLoading) { return ( <> {!embedded &&
    } @@ -3676,203 +3723,227 @@ const ViewProject = ({ return ( {() => ( - <> - {!embedded &&
    } - -
    + + <> + {!embedded &&
    } +
    - {/* Tabs selector */} - - {activeTab !== PROJECT_TABS.alerts && - (activeTab !== PROJECT_TABS.sessions || !activePSID) && - (activeFunnel || activeTab !== PROJECT_TABS.funnels) && ( - <> -
    -
    -

    - {/* If tab is funnels - then display a funnel name, otherwise a project name */} - {activeTab === PROJECT_TABS.funnels ? activeFunnel?.name : name} -

    - {activeTab !== PROJECT_TABS.funnels && ( - - )} -
    -
    - {activeTab !== PROJECT_TABS.funnels && ( - <> -
    - -
    - {!isSelfhosted && !isActiveCompare && ( +
    + {/* Tabs selector */} + + {activeTab !== PROJECT_TABS.alerts && + activeTab !== PROJECT_TABS.uptime && + (activeTab !== PROJECT_TABS.sessions || !activePSID) && + (activeFunnel || activeTab !== PROJECT_TABS.funnels) && ( + <> +
    +
    +

    + {/* If tab is funnels - then display a funnel name, otherwise a project name */} + {activeTab === PROJECT_TABS.funnels ? activeFunnel?.name : name} +

    + {activeTab !== PROJECT_TABS.funnels && ( + + )} +
    +
    + {activeTab !== PROJECT_TABS.funnels && ( + <> +
    + +
    + {!isSelfhosted && !isActiveCompare && ( +
    + +
    + )}
    - )} -
    - -
    - {activeTab === PROJECT_TABS.traffic && ( - loadProjectViews()} - loading={projectViewsLoading || projectViewsLoading === null} - selectItemClassName={ - !allowedToManage && - !(projectViewsLoading || projectViewsLoading === null) && - _isEmpty(projectViews) - ? 'block px-4 py-2 text-sm text-gray-700 dark:border-gray-800 dark:bg-slate-800 dark:text-gray-50' - : undefined - } - items={_filter( - [ - ...projectViews, - allowedToManage && { - id: 'add-a-view', - name: t('project.addAView'), - createView: true, - }, + {activeTab === PROJECT_TABS.traffic && ( + loadProjectViews()} + loading={projectViewsLoading || projectViewsLoading === null} + selectItemClassName={ !allowedToManage && - _isEmpty(projectViews) && { - id: 'no-views', - name: t('project.noViewsYet'), - notClickable: true, - }, - ], - (x) => !!x, - )} - title={[]} - labelExtractor={(item, close) => { - // @ts-expect-error - if (item.createView) { - return item.name + !(projectViewsLoading || projectViewsLoading === null) && + _isEmpty(projectViews) + ? 'block px-4 py-2 text-sm text-gray-700 dark:border-gray-800 dark:bg-slate-800 dark:text-gray-50' + : undefined } + items={_filter( + [ + ...projectViews, + allowedToManage && { + id: 'add-a-view', + name: t('project.addAView'), + createView: true, + }, + !allowedToManage && + _isEmpty(projectViews) && { + id: 'no-views', + name: t('project.noViewsYet'), + notClickable: true, + }, + ], + (x) => !!x, + )} + title={[]} + labelExtractor={(item, close) => { + // @ts-expect-error + if (item.createView) { + return item.name + } - if (item.id === 'no-views') { - return {item.name} - } + if (item.id === 'no-views') { + return {item.name} + } - return ( - - {item.name} - {allowedToManage && ( - - { - e.stopPropagation() - setProjectViewToUpdate(item) - close() - setIsAddAViewOpened(true) - }} - /> - { - e.stopPropagation() - close() - onProjectViewDelete(item.id) - }} - /> - - )} - - ) - }} - keyExtractor={(item) => item.id} - onSelect={(item: IProjectView, e) => { - // @ts-expect-error - if (item.createView) { - e?.stopPropagation() - setIsAddAViewOpened(true) + return ( + + {item.name} + {allowedToManage && ( + + { + e.stopPropagation() + setProjectViewToUpdate(item) + close() + setIsAddAViewOpened(true) + }} + /> + { + e.stopPropagation() + close() + onProjectViewDelete(item.id) + }} + /> + + )} + + ) + }} + keyExtractor={(item) => item.id} + onSelect={(item: IProjectView, e) => { + // @ts-expect-error + if (item.createView) { + e?.stopPropagation() + setIsAddAViewOpened(true) - return - } + return + } - if (item.filters && !_isEmpty(item.filters)) { - onFilterSearch(item.filters, true) - } + if (item.filters && !_isEmpty(item.filters)) { + onFilterSearch(item.filters, true) + } - if (item.customEvents && !_isEmpty(item.customEvents)) { - onCustomMetric(item.customEvents) - } - }} - chevron='mini' - buttonClassName='!p-2 rounded-md hover:bg-white hover:shadow-sm dark:hover:bg-slate-800 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 focus:dark:ring-gray-200 focus:dark:border-gray-200' - headless - /> - )} - {activeTab !== PROJECT_TABS.funnels && - activeTab !== PROJECT_TABS.sessions && - activeTab !== PROJECT_TABS.errors && ( + if (item.customEvents && !_isEmpty(item.customEvents)) { + onCustomMetric(item.customEvents) + } + }} + chevron='mini' + buttonClassName='!p-2 rounded-md hover:bg-white hover:shadow-sm dark:hover:bg-slate-800 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 focus:dark:ring-gray-200 focus:dark:border-gray-200' + headless + /> + )} + {_includes([PROJECT_TABS.traffic, PROJECT_TABS.performance], activeTab) && ( )} -
    - - -
    - - )} - {activeTab === PROJECT_TABS.traffic && !isPanelsDataEmpty && ( - { - return !_includes(FILTER_CHART_METRICS_MAPPING_FOR_COMPARE, el.id) - }) - : chartMetrics - } - title={t('project.metricVis')} - labelExtractor={(pair) => { - const { label, id: pairID, active, conflicts } = pair - - const conflicted = isConflicted(conflicts) + + +
    + + )} + {activeTab === PROJECT_TABS.traffic && !isPanelsDataEmpty && ( + { + return !_includes(FILTER_CHART_METRICS_MAPPING_FOR_COMPARE, el.id) + }) + : chartMetrics + } + title={t('project.metricVis')} + labelExtractor={(pair) => { + const { label, id: pairID, active, conflicts } = pair + + const conflicted = isConflicted(conflicts) + + if (pairID === CHART_METRICS_MAPPING.customEvents) { + if (_isEmpty(panelsData.customs)) { + return ( + + + {label} + + ) + } - if (pairID === CHART_METRICS_MAPPING.customEvents) { - if (_isEmpty(panelsData.customs)) { return ( - - - {label} - + ( + CUSTOM_EV_DROPDOWN_MAX_VISIBLE_LENGTH ? ( + + {_truncate(event.label, { + length: CUSTOM_EV_DROPDOWN_MAX_VISIBLE_LENGTH, + })} + + ) : ( + event.label + ) + } + onChange={() => { + switchCustomEventChart(event.id) + }} + checked={event.active} + /> + )} + buttonClassName='group-hover:bg-gray-200 dark:group-hover:bg-slate-700 px-4 py-2 inline-flex w-full bg-white text-sm font-medium text-gray-700 dark:text-gray-50 dark:border-gray-800 dark:bg-slate-800' + keyExtractor={(event) => event.id} + onSelect={(event, e) => { + e?.stopPropagation() + e?.preventDefault() + + switchCustomEventChart(event.id) + }} + chevron='mini' + headless + /> ) } return ( - ( - CUSTOM_EV_DROPDOWN_MAX_VISIBLE_LENGTH ? ( - - {_truncate(event.label, { - length: CUSTOM_EV_DROPDOWN_MAX_VISIBLE_LENGTH, - })} - - ) : ( - event.label - ) - } - onChange={() => { - switchCustomEventChart(event.id) - }} - checked={event.active} - /> - )} - buttonClassName='group-hover:bg-gray-200 dark:group-hover:bg-slate-700 px-4 py-2 inline-flex w-full bg-white text-sm font-medium text-gray-700 dark:text-gray-50 dark:border-gray-800 dark:bg-slate-800' - keyExtractor={(event) => event.id} - onSelect={(event, e) => { - e?.stopPropagation() - e?.preventDefault() - - switchCustomEventChart(event.id) + { + switchTrafficChartMetric(pairID, conflicts) }} - chevron='mini' - headless /> ) - } + }} + buttonClassName='!px-2.5' + selectItemClassName='group text-gray-700 dark:text-gray-50 dark:border-gray-800 dark:bg-slate-800 block text-sm cursor-pointer hover:bg-gray-200 dark:hover:bg-slate-700' + keyExtractor={(pair) => pair.id} + onSelect={({ id: pairID, conflicts }) => { + switchTrafficChartMetric(pairID, conflicts) + }} + chevron='mini' + headless + /> + )} + {activeTab === PROJECT_TABS.errors && + allowedToManage && + activeError && + activeError?.details?.status !== 'resolved' && ( + + )} + {activeTab === PROJECT_TABS.errors && + allowedToManage && + activeError && + activeError?.details?.status === 'resolved' && ( + + )} + {activeTab === PROJECT_TABS.errors && !activeError && ( + { + const { label, active, id: pairID } = pair - return ( - { - switchTrafficChartMetric(pairID, conflicts) - }} - /> - ) - }} - buttonClassName='!px-2.5' - selectItemClassName='group text-gray-700 dark:text-gray-50 dark:border-gray-800 dark:bg-slate-800 block text-sm cursor-pointer hover:bg-gray-200 dark:hover:bg-slate-700' - keyExtractor={(pair) => pair.id} - onSelect={({ id: pairID, conflicts }) => { - switchTrafficChartMetric(pairID, conflicts) - }} - chevron='mini' - headless - /> - )} - {activeTab === PROJECT_TABS.errors && - allowedToManage && - activeError && - activeError?.details?.status !== 'resolved' && ( - + return ( + switchActiveErrorFilter(pairID)} + /> + ) + }} + buttonClassName='!px-2.5' + selectItemClassName='group text-gray-700 dark:text-gray-50 dark:border-gray-800 dark:bg-slate-800 block text-sm cursor-pointer hover:bg-gray-200 dark:hover:bg-slate-700' + keyExtractor={(pair) => pair.id} + onSelect={({ id: pairID }) => { + switchActiveErrorFilter(pairID) + }} + chevron='mini' + headless + /> )} - {activeTab === PROJECT_TABS.errors && - allowedToManage && - activeError && - activeError?.details?.status === 'resolved' && ( + {activeTab === PROJECT_TABS.funnels && ( )} - {activeTab === PROJECT_TABS.errors && !activeError && ( - { - const { label, active, id: pairID } = pair - - return ( - switchActiveErrorFilter(pairID)} - /> - ) - }} - buttonClassName='!px-2.5' - selectItemClassName='group text-gray-700 dark:text-gray-50 dark:border-gray-800 dark:bg-slate-800 block text-sm cursor-pointer hover:bg-gray-200 dark:hover:bg-slate-700' - keyExtractor={(pair) => pair.id} - onSelect={({ id: pairID }) => { - switchActiveErrorFilter(pairID) - }} - chevron='mini' - headless - /> - )} - {activeTab === PROJECT_TABS.funnels && ( - - )} - {activeTab === PROJECT_TABS.performance && !isPanelsDataEmptyPerf && ( - - { - _find(chartMetricsPerf, ({ id: chartId }) => chartId === activeChartMetricsPerf) - ?.label - } -

    - } - labelExtractor={(pair) => pair.label} - keyExtractor={(pair) => pair.id} - onSelect={({ id: pairID }) => { - setActiveChartMetricsPerf(pairID) - }} - buttonClassName='!px-2.5' - chevron='mini' - headless - /> - )} - {activeTab === PROJECT_TABS.performance && !isPanelsDataEmptyPerf && ( - - {_find(chartMeasuresPerf, ({ id: chartId }) => chartId === activePerfMeasure)?.label} -

    - } - labelExtractor={(pair) => pair.label} - keyExtractor={(pair) => pair.id} - onSelect={({ id: pairID }) => { - onMeasureChange(pairID) - }} - buttonClassName='!px-2.5' - chevron='mini' - headless - /> - )} - { - if (pair.period === PERIOD_PAIRS_COMPARE.COMPARE) { - if (activeTab === PROJECT_TABS.alerts) { - return - } - - if (isActiveCompare) { - compareDisable() - } else { - setIsActiveCompare(true) + {activeTab === PROJECT_TABS.performance && !isPanelsDataEmptyPerf && ( + + { + _find(chartMetricsPerf, ({ id: chartId }) => chartId === activeChartMetricsPerf) + ?.label + } +

    } - - return - } - - if (pair.isCustomDate) { - setTimeout(() => { - // @ts-ignore - refCalendar.current.openCalendar() - }, 100) - } else { - setPeriodPairs(tbPeriodPairs(t, undefined, undefined, language)) - setDateRange(null) - updatePeriod(pair) - } - }} - /> - {isActiveCompare && activeTab !== PROJECT_TABS.errors && ( - <> -
    - vs -
    + labelExtractor={(pair) => pair.label} + keyExtractor={(pair) => pair.id} + onSelect={({ id: pairID }) => { + setActiveChartMetricsPerf(pairID) + }} + buttonClassName='!px-2.5' + chevron='mini' + headless + /> + )} + {activeTab === PROJECT_TABS.performance && !isPanelsDataEmptyPerf && ( + {_find(chartMeasuresPerf, ({ id: chartId }) => chartId === activePerfMeasure)?.label} +

    + } labelExtractor={(pair) => pair.label} - keyExtractor={(pair) => pair.label} - onSelect={(pair) => { - if (pair.period === PERIOD_PAIRS_COMPARE.DISABLE) { - compareDisable() + keyExtractor={(pair) => pair.id} + onSelect={({ id: pairID }) => { + onMeasureChange(pairID) + }} + buttonClassName='!px-2.5' + chevron='mini' + headless + /> + )} + { + if (pair.period === PERIOD_PAIRS_COMPARE.COMPARE) { + if (activeTab === PROJECT_TABS.alerts) { return } - if (pair.period === PERIOD_PAIRS_COMPARE.CUSTOM) { - setTimeout(() => { - // @ts-ignore - refCalendarCompare.current.openCalendar() - }, 100) + if (isActiveCompare) { + compareDisable() } else { - setPeriodPairsCompare(tbPeriodPairsCompare(t, undefined, language)) - setDateRangeCompare(null) - setActivePeriodCompare(pair.period) + setIsActiveCompare(true) } - }} - chevron='mini' - headless - /> - - )} - - { - setDateRangeCompare(date) - setActivePeriodCompare(PERIOD_PAIRS_COMPARE.CUSTOM) - setPeriodPairsCompare(tbPeriodPairsCompare(t, date, language)) - }} - value={dateRangeCompare || []} - maxDateMonths={MAX_MONTHS_IN_PAST} - maxRange={maxRangeCompare} - /> -
    -
    - {activeTab === PROJECT_TABS.funnels && ( - - )} - - )} - {activeTab === PROJECT_TABS.alerts && (isSharedProject || !project?.isOwner || !authenticated) && ( -
    -
    - -

    {t('dashboard.alerts')}

    -
    -

    {t('dashboard.alertsDesc')}

    - - {t('common.getStarted')} - -
    - )} - {activeTab === PROJECT_TABS.funnels && !activeFunnel && !_isEmpty(project.funnels) && ( - { - if (funnel) { - setFunnelToEdit(funnel) - setIsNewFunnelOpened(true) - return - } - - setIsNewFunnelOpened(true) - }} - openFunnel={setActiveFunnel} - funnels={project.funnels} - deleteFunnel={onFunnelDelete} - loading={funnelActionLoading} - authenticated={authenticated} - allowedToManage={allowedToManage} - /> - )} - {activeTab === PROJECT_TABS.funnels && !activeFunnel && _isEmpty(project.funnels) && ( -
    -
    - -

    {t('dashboard.funnels')}

    -
    -

    {t('dashboard.funnelsDesc')}

    - {authenticated ? ( - - ) : ( + + return + } + + if (pair.isCustomDate) { + setTimeout(() => { + // @ts-ignore + refCalendar.current.openCalendar() + }, 100) + } else { + setPeriodPairs(tbPeriodPairs(t, undefined, undefined, language)) + setDateRange(null) + updatePeriod(pair) + } + }} + /> + {isActiveCompare && activeTab !== PROJECT_TABS.errors && ( + <> +
    + vs +
    + pair.label} + keyExtractor={(pair) => pair.label} + onSelect={(pair) => { + if (pair.period === PERIOD_PAIRS_COMPARE.DISABLE) { + compareDisable() + return + } + + if (pair.period === PERIOD_PAIRS_COMPARE.CUSTOM) { + setTimeout(() => { + // @ts-ignore + refCalendarCompare.current.openCalendar() + }, 100) + } else { + setPeriodPairsCompare(tbPeriodPairsCompare(t, undefined, language)) + setDateRangeCompare(null) + setActivePeriodCompare(pair.period) + } + }} + chevron='mini' + headless + /> + + )} + + { + setDateRangeCompare(date) + setActivePeriodCompare(PERIOD_PAIRS_COMPARE.CUSTOM) + setPeriodPairsCompare(tbPeriodPairsCompare(t, date, language)) + }} + value={dateRangeCompare || []} + maxDateMonths={MAX_MONTHS_IN_PAST} + maxRange={maxRangeCompare} + /> +
    +
    + {activeTab === PROJECT_TABS.funnels && ( + + )} + + )} + {activeTab === PROJECT_TABS.alerts && (isSharedProject || !project?.isOwner || !authenticated) && ( +
    +
    + +

    {t('dashboard.alerts')}

    +
    +

    {t('dashboard.alertsDesc')}

    {t('common.getStarted')} - )} -
    - )} - {activeTab === PROJECT_TABS.sessions && !activePSID && ( - <> - - {(sessionsLoading === null || sessionsLoading) && _isEmpty(sessions) && } - {typeof sessionsLoading === 'boolean' && !sessionsLoading && _isEmpty(sessions) && ( - - )} - { - setActivePSID(psid) +
    + )} + {activeTab === PROJECT_TABS.funnels && !activeFunnel && !_isEmpty(project.funnels) && ( + { + if (funnel) { + setFunnelToEdit(funnel) + setIsNewFunnelOpened(true) + return + } + + setIsNewFunnelOpened(true) }} - timeFormat={timeFormat} + openFunnel={setActiveFunnel} + funnels={project.funnels} + deleteFunnel={onFunnelDelete} + loading={funnelActionLoading} + authenticated={authenticated} + allowedToManage={allowedToManage} /> - {canLoadMoreSessions && ( + )} + {activeTab === PROJECT_TABS.funnels && !activeFunnel && _isEmpty(project.funnels) && ( +
    +
    + +

    {t('dashboard.funnels')}

    +
    +

    {t('dashboard.funnelsDesc')}

    + {authenticated ? ( + + ) : ( + + {t('common.getStarted')} + + )} +
    + )} + {activeTab === PROJECT_TABS.sessions && !activePSID && ( + <> + + {(sessionsLoading === null || sessionsLoading) && _isEmpty(sessions) && } + {typeof sessionsLoading === 'boolean' && !sessionsLoading && _isEmpty(sessions) && ( + + )} + { + setActivePSID(psid) + }} + timeFormat={timeFormat} + /> + {canLoadMoreSessions && ( + + )} + + )} + {activeTab === PROJECT_TABS.sessions && activePSID && ( + <> - )} - - )} - {activeTab === PROJECT_TABS.sessions && activePSID && ( - <> - - {activeSession?.details && } - {!_isEmpty(activeSession?.chart) && ( - } + {!_isEmpty(activeSession?.chart) && ( + + )} + + {_isEmpty(activeSession) && sessionLoading && } + {activeSession !== null && + _isEmpty(activeSession?.chart) && + _isEmpty(activeSession?.pages) && + !sessionLoading && } + + )} + {activeTab === PROJECT_TABS.errors && !activeEID && ( + <> + - )} - - {_isEmpty(activeSession) && sessionLoading && } - {activeSession !== null && - _isEmpty(activeSession?.chart) && - _isEmpty(activeSession?.pages) && - !sessionLoading && } - - )} - {activeTab === PROJECT_TABS.errors && !activeEID && ( - <> - - {(errorsLoading === null || errorsLoading) && _isEmpty(errors) && } - {typeof errorsLoading === 'boolean' && !errorsLoading && _isEmpty(errors) && ( - - )} - { - setActiveEID(eidToLoad) - setErrorLoading(true) - }} - /> - {canLoadMoreErrors && ( + {(errorsLoading === null || errorsLoading) && _isEmpty(errors) && } + {typeof errorsLoading === 'boolean' && !errorsLoading && _isEmpty(errors) && ( + + )} + { + setActiveEID(eidToLoad) + setErrorLoading(true) + }} + /> + {canLoadMoreErrors && ( + + )} + + )} + {activeTab === PROJECT_TABS.errors && activeEID && ( + <> - )} - - )} - {activeTab === PROJECT_TABS.errors && activeEID && ( - <> - - {activeError?.details && } - {activeError?.chart && ( - - )} -
    - {!_isEmpty(activeError?.params) && - _map(ERROR_PANELS_ORDER, (type: keyof typeof tnMapping) => { - const panelName = tnMapping[type] - // @ts-ignore - const panelIcon = panelIconMapping[type] + {activeError?.details && } + {activeError?.chart && ( + + )} +
    + {!_isEmpty(activeError?.params) && + _map(ERROR_PANELS_ORDER, (type: keyof typeof tnMapping) => { + const panelName = tnMapping[type] + // @ts-ignore + const panelIcon = panelIconMapping[type] - if (type === 'cc') { - const ccPanelName = tnMapping[countryActiveTab] + if (type === 'cc') { + const ccPanelName = tnMapping[countryActiveTab] - const rowMapper = (entry: ICountryEntry) => { - const { name: entryName, cc } = entry + const rowMapper = (entry: ICountryEntry) => { + const { name: entryName, cc } = entry - if (cc) { - return + if (cc) { + return + } + + return } - return + return ( + } + data={activeError.params[countryActiveTab]} + rowMapper={rowMapper} + /> + ) } - return ( - } - data={activeError.params[countryActiveTab]} - rowMapper={rowMapper} - /> - ) - } + if (type === 'br') { + const rowMapper = (entry: any) => { + const { name: entryName } = entry + // @ts-ignore + const logoUrl = BROWSER_LOGO_MAP[entryName] - if (type === 'br') { - const rowMapper = (entry: any) => { - const { name: entryName } = entry - // @ts-ignore - const logoUrl = BROWSER_LOGO_MAP[entryName] + if (!logoUrl) { + return ( + <> + +   + {entryName} + + ) + } - if (!logoUrl) { return ( <> - +   {entryName} @@ -4512,132 +4592,140 @@ const ViewProject = ({ } return ( - <> - -   - {entryName} - + ) } - return ( - - ) - } + if (type === 'os') { + const rowMapper = (entry: any) => { + const { name: entryName } = entry + // @ts-ignore + const logoPathLight = OS_LOGO_MAP[entryName] + // @ts-ignore + const logoPathDark = OS_LOGO_MAP_DARK[entryName] + + let logoPath = _theme === 'dark' ? logoPathDark : logoPathLight + logoPath ||= logoPathLight - if (type === 'os') { - const rowMapper = (entry: any) => { - const { name: entryName } = entry - // @ts-ignore - const logoPathLight = OS_LOGO_MAP[entryName] - // @ts-ignore - const logoPathDark = OS_LOGO_MAP_DARK[entryName] + if (!logoPath) { + return ( + <> + +   + {entryName} + + ) + } - let logoPath = _theme === 'dark' ? logoPathDark : logoPathLight - logoPath ||= logoPathLight + const logoUrl = `/${logoPath}` - if (!logoPath) { return ( <> - +   {entryName} ) } - const logoUrl = `/${logoPath}` + return ( + + ) + } + if (type === 'dv') { return ( - <> - -   - {entryName} - + ) } - return ( - - ) - } + if (type === 'pg') { + return ( + { + if (!entryName) { + return _toUpper(t('project.redactedPage')) + } - if (type === 'dv') { - return ( - - ) - } + let decodedUri = entryName as string - if (type === 'pg') { - return ( - { - if (!entryName) { - return _toUpper(t('project.redactedPage')) - } + try { + decodedUri = decodeURIComponent(entryName) + } catch (_) { + // do nothing + } - let decodedUri = entryName as string + return decodedUri + }} + name={pgPanelNameMapping[pgActiveFragment]} + data={activeError.params[type]} + period={period} + activeTab={activeTab} + pid={id} + timeBucket={timeBucket} + filters={filters} + from={dateRange ? getFormatDate(dateRange[0]) : null} + to={dateRange ? getFormatDate(dateRange[1]) : null} + timezone={timezone} + /> + ) + } - try { - decodedUri = decodeURIComponent(entryName) - } catch (_) { - // do nothing + if (type === 'lc') { + return ( + + getLocaleDisplayName(entryName, language) } + /> + ) + } - return decodedUri - }} - name={pgPanelNameMapping[pgActiveFragment]} - data={activeError.params[type]} - period={period} - activeTab={activeTab} - pid={id} - timeBucket={timeBucket} - filters={filters} - from={dateRange ? getFormatDate(dateRange[0]) : null} - to={dateRange ? getFormatDate(dateRange[1]) : null} - timezone={timezone} - /> - ) - } - - if (type === 'lc') { return ( - getLocaleDisplayName(entryName, language) - } /> ) - } - - return ( - - ) - })} -
    - {_isEmpty(activeError) && errorLoading && } - {!errorLoading && _isEmpty(activeError) && } - - )} - {activeTab === PROJECT_TABS.alerts && !isSharedProject && project?.isOwner && authenticated && ( - - )} - {analyticsLoading && (activeTab === PROJECT_TABS.traffic || activeTab === PROJECT_TABS.performance) && ( - - )} - {isPanelsDataEmpty && activeTab === PROJECT_TABS.traffic && ( - - )} - {isPanelsDataEmptyPerf && activeTab === PROJECT_TABS.performance && ( - - )} - {activeTab === PROJECT_TABS.traffic && ( -
    - {!_isEmpty(overall) && ( -
    - - {!_isEmpty(panelsData.meta) && - _map(panelsData.meta, ({ key, current, previous }) => ( - - - `${type === 'badge' && value > 0 ? '+' : ''}${nLocaleFormatter(value)}` - } - /> - - `${type === 'badge' && value > 0 ? '+' : ''}${nLocaleFormatter(value)}` - } - /> - - ))} + })}
    - )} -
    -
    -
    - - onRemoveCustomMetric(id)} - resetMetrics={resetCustomMetrics} - /> - {dataLoading && ( -
    -
    -
    -
    + {_isEmpty(activeError) && errorLoading && } + {!errorLoading && _isEmpty(activeError) && } + + )} + {activeTab === PROJECT_TABS.alerts && !isSharedProject && project?.isOwner && authenticated && ( + + )} + {activeTab === PROJECT_TABS.uptime && } + {analyticsLoading && (activeTab === PROJECT_TABS.traffic || activeTab === PROJECT_TABS.performance) && ( + + )} + {isPanelsDataEmpty && activeTab === PROJECT_TABS.traffic && ( + + )} + {isPanelsDataEmptyPerf && activeTab === PROJECT_TABS.performance && ( + + )} + {activeTab === PROJECT_TABS.traffic && ( +
    + {!_isEmpty(overall) && ( +
    + + {!_isEmpty(panelsData.meta) && + _map(panelsData.meta, ({ key, current, previous }) => ( + + + `${type === 'badge' && value > 0 ? '+' : ''}${nLocaleFormatter(value)}` + } + /> + + `${type === 'badge' && value > 0 ? '+' : ''}${nLocaleFormatter(value)}` + } + /> + + ))}
    + )} +
    +
    - )} -
    - {!_isEmpty(panelsData.types) && - _map(TRAFFIC_PANELS_ORDER, (type: keyof typeof tnMapping) => { - const panelName = tnMapping[type] - // @ts-ignore - const panelIcon = panelIconMapping[type] - const customTabs = _filter(customPanelTabs, (tab) => tab.panelID === type) - - if (type === 'cc') { - const ccPanelName = tnMapping[countryActiveTab] - - const rowMapper = (entry: ICountryEntry) => { - const { name: entryName, cc } = entry - - if (cc) { - return + + onRemoveCustomMetric(id)} + resetMetrics={resetCustomMetrics} + /> + {dataLoading && ( +
    +
    +
    +
    +
    +
    + )} +
    + {!_isEmpty(panelsData.types) && + _map(TRAFFIC_PANELS_ORDER, (type: keyof typeof tnMapping) => { + const panelName = tnMapping[type] + // @ts-ignore + const panelIcon = panelIconMapping[type] + const customTabs = _filter(customPanelTabs, (tab) => tab.panelID === type) + + if (type === 'cc') { + const ccPanelName = tnMapping[countryActiveTab] + + const rowMapper = (entry: ICountryEntry) => { + const { name: entryName, cc } = entry + + if (cc) { + return + } + + return } - return + return ( + } + data={panelsData.data[countryActiveTab]} + customTabs={customTabs} + rowMapper={rowMapper} + /> + ) } - return ( - } - data={panelsData.data[countryActiveTab]} - customTabs={customTabs} - rowMapper={rowMapper} - /> - ) - } + if (type === 'br') { + const rowMapper = (entry: any) => { + const { name: entryName } = entry + // @ts-ignore + const logoUrl = BROWSER_LOGO_MAP[entryName] - if (type === 'br') { - const rowMapper = (entry: any) => { - const { name: entryName } = entry - // @ts-ignore - const logoUrl = BROWSER_LOGO_MAP[entryName] + if (!logoUrl) { + return ( + <> + +   + {entryName} + + ) + } - if (!logoUrl) { return ( <> - +   {entryName} @@ -4800,174 +4881,183 @@ const ViewProject = ({ } return ( - <> - -   - {entryName} - + ) } - return ( - - ) - } + if (type === 'os') { + const rowMapper = (entry: any) => { + const { name: entryName } = entry + // @ts-ignore + const logoPathLight = OS_LOGO_MAP[entryName] + // @ts-ignore + const logoPathDark = OS_LOGO_MAP_DARK[entryName] + + let logoPath = _theme === 'dark' ? logoPathDark : logoPathLight + logoPath ||= logoPathLight - if (type === 'os') { - const rowMapper = (entry: any) => { - const { name: entryName } = entry - // @ts-ignore - const logoPathLight = OS_LOGO_MAP[entryName] - // @ts-ignore - const logoPathDark = OS_LOGO_MAP_DARK[entryName] + if (!logoPath) { + return ( + <> + +   + {entryName} + + ) + } - let logoPath = _theme === 'dark' ? logoPathDark : logoPathLight - logoPath ||= logoPathLight + const logoUrl = `/${logoPath}` - if (!logoPath) { return ( <> - +   {entryName} ) } - const logoUrl = `/${logoPath}` + return ( + + ) + } + if (type === 'dv') { return ( - <> - -   - {entryName} - + ) } - return ( - - ) - } + if (type === 'ref') { + return ( + } + /> + ) + } - if (type === 'dv') { - return ( - - ) - } + if (type === 'so') { + const ccPanelName = tnMapping[utmActiveTab] - if (type === 'ref') { - return ( - } - /> - ) - } + return ( + } + data={panelsData.data[utmActiveTab]} + customTabs={customTabs} + // @ts-ignore + rowMapper={({ name: entryName }) => decodeURIComponent(entryName)} + /> + ) + } - if (type === 'so') { - const ccPanelName = tnMapping[utmActiveTab] + if (type === 'pg') { + return ( + { + if (!entryName) { + return _toUpper(t('project.redactedPage')) + } - return ( - } - data={panelsData.data[utmActiveTab]} - customTabs={customTabs} - // @ts-ignore - rowMapper={({ name: entryName }) => decodeURIComponent(entryName)} - /> - ) - } + let decodedUri = entryName as string - if (type === 'pg') { - return ( - { - if (!entryName) { - return _toUpper(t('project.redactedPage')) - } + try { + decodedUri = decodeURIComponent(entryName) + } catch (_) { + // do nothing + } - let decodedUri = entryName as string + return decodedUri + }} + name={pgPanelNameMapping[pgActiveFragment]} + data={panelsData.data[type]} + customTabs={customTabs} + period={period} + activeTab={activeTab} + pid={id} + timeBucket={timeBucket} + filters={filters} + from={dateRange ? getFormatDate(dateRange[0]) : null} + to={dateRange ? getFormatDate(dateRange[1]) : null} + timezone={timezone} + /> + ) + } - try { - decodedUri = decodeURIComponent(entryName) - } catch (_) { - // do nothing + if (type === 'lc') { + return ( + + getLocaleDisplayName(entryName, language) } + customTabs={customTabs} + /> + ) + } - return decodedUri - }} - name={pgPanelNameMapping[pgActiveFragment]} - data={panelsData.data[type]} - customTabs={customTabs} - period={period} - activeTab={activeTab} - pid={id} - timeBucket={timeBucket} - filters={filters} - from={dateRange ? getFormatDate(dateRange[0]) : null} - to={dateRange ? getFormatDate(dateRange[1]) : null} - timezone={timezone} - /> - ) - } - - if (type === 'lc') { return ( - getLocaleDisplayName(entryName, language) - } customTabs={customTabs} /> ) - } - - return ( - - ) - })} - {!_isEmpty(panelsData.data) && ( - tab.panelID === 'ce')} - getCustomEventMetadata={getCustomEventMetadata} - getPropertyMetadata={_getPropertyMetadata} + })} + {!_isEmpty(panelsData.data) && ( + tab.panelID === 'ce')} + getCustomEventMetadata={getCustomEventMetadata} + getPropertyMetadata={_getPropertyMetadata} + /> + )} +
    +
    + )} + {activeTab === PROJECT_TABS.performance && ( +
    + {!_isEmpty(overallPerformance) && ( + )} -
    -
    - )} - {activeTab === PROJECT_TABS.performance && ( -
    - {!_isEmpty(overallPerformance) && ( - +
    +
    + - )} -
    -
    -
    - - {dataLoading && ( -
    -
    -
    -
    + {dataLoading && ( +
    +
    +
    +
    +
    -
    - )} -
    - {!_isEmpty(panelsDataPerf.types) && - _map(PERFORMANCE_PANELS_ORDER, (type: keyof typeof tnMapping) => { - const panelName = tnMapping[type] - // @ts-ignore - const panelIcon = panelIconMapping[type] - const customTabs = _filter(customPanelTabs, (tab) => tab.panelID === type) - - if (type === 'cc') { - const ccPanelName = tnMapping[countryActiveTab] - - const rowMapper = (entry: ICountryEntry) => { - const { name: entryName, cc } = entry - - if (cc) { - return + )} +
    + {!_isEmpty(panelsDataPerf.types) && + _map(PERFORMANCE_PANELS_ORDER, (type: keyof typeof tnMapping) => { + const panelName = tnMapping[type] + // @ts-ignore + const panelIcon = panelIconMapping[type] + const customTabs = _filter(customPanelTabs, (tab) => tab.panelID === type) + + if (type === 'cc') { + const ccPanelName = tnMapping[countryActiveTab] + + const rowMapper = (entry: ICountryEntry) => { + const { name: entryName, cc } = entry + + if (cc) { + return + } + + return } - return + return ( + } + activeTab={activeTab} + data={panelsDataPerf.data[countryActiveTab]} + customTabs={customTabs} + rowMapper={rowMapper} + // @ts-ignore + valueMapper={(value) => getStringFromTime(getTimeFromSeconds(value), true)} + /> + ) } - return ( - } - activeTab={activeTab} - data={panelsDataPerf.data[countryActiveTab]} - customTabs={customTabs} - rowMapper={rowMapper} - // @ts-ignore - valueMapper={(value) => getStringFromTime(getTimeFromSeconds(value), true)} - /> - ) - } + if (type === 'dv') { + return ( + getStringFromTime(getTimeFromSeconds(value), true)} + capitalize + /> + ) + } - if (type === 'dv') { - return ( - getStringFromTime(getTimeFromSeconds(value), true)} - capitalize - /> - ) - } + if (type === 'pg') { + return ( + getStringFromTime(getTimeFromSeconds(value), true)} + // @ts-ignore + rowMapper={({ name: entryName }) => { + // todo: add uppercase + return entryName || t('project.redactedPage') + }} + /> + ) + } - if (type === 'pg') { - return ( - { + const { name: entryName } = entry // @ts-ignore - valueMapper={(value) => getStringFromTime(getTimeFromSeconds(value), true)} - // @ts-ignore - rowMapper={({ name: entryName }) => { - // todo: add uppercase - return entryName || t('project.redactedPage') - }} - /> - ) - } + const logoUrl = BROWSER_LOGO_MAP[entryName] - if (type === 'br') { - const rowMapper = (entry: any) => { - const { name: entryName } = entry - // @ts-ignore - const logoUrl = BROWSER_LOGO_MAP[entryName] + if (!logoUrl) { + return ( + <> + +   + {entryName} + + ) + } - if (!logoUrl) { return ( <> - +   {entryName} @@ -5144,11 +5225,20 @@ const ViewProject = ({ } return ( - <> - -   - {entryName} - + getStringFromTime(getTimeFromSeconds(value), true)} + rowMapper={rowMapper} + /> ) } @@ -5165,113 +5255,96 @@ const ViewProject = ({ customTabs={customTabs} // @ts-ignore valueMapper={(value) => getStringFromTime(getTimeFromSeconds(value), true)} - rowMapper={rowMapper} /> ) - } - - return ( - getStringFromTime(getTimeFromSeconds(value), true)} - /> - ) - })} -
    -
    - )} - {activeTab === PROJECT_TABS.funnels && ( -
    -
    -
    + })} +
    - {dataLoading && ( -
    -
    -
    -
    -
    + )} + {activeTab === PROJECT_TABS.funnels && ( +
    +
    +
    - )} -
    - )} + {dataLoading && ( +
    +
    +
    +
    +
    +
    + )} +
    + )} +
    -
    - setIsForecastOpened(false)} - onSubmit={onForecastSubmit} - activeTB={t(`project.${timeBucket}`)} - tb={timeBucket} - /> - setIsHotkeysHelpOpened(false)} /> - { - setIsNewFunnelOpened(false) - setFunnelToEdit(undefined) - }} - onSubmit={async (name: string, steps: string[]) => { - if (funnelToEdit) { - await onFunnelEdit(funnelToEdit.id, name, steps) - return - } + setIsForecastOpened(false)} + onSubmit={onForecastSubmit} + activeTB={t(`project.${timeBucket}`)} + tb={timeBucket} + /> + setIsHotkeysHelpOpened(false)} /> + { + setIsNewFunnelOpened(false) + setFunnelToEdit(undefined) + }} + onSubmit={async (name: string, steps: string[]) => { + if (funnelToEdit) { + await onFunnelEdit(funnelToEdit.id, name, steps) + return + } - await onFunnelCreate(name, steps) - }} - loading={funnelActionLoading} - /> - - { - setIsAddAViewOpened(show) - setProjectViewToUpdate(undefined) - }} - onSubmit={() => { - setProjectViews([]) - setProjectViewsLoading(null) - setProjectViewToUpdate(undefined) - }} - defaultView={projectViewToUpdate} - showError={showError} - pid={id} - tnMapping={tnMapping} - /> - {!embedded &&