diff --git a/api/routers/core.py b/api/routers/core.py index 076ca81f4..8aacdb99b 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -36,8 +36,7 @@ def get_session2(projectId: int, sessionId: Union[int, str], context: schemas.Cu include_fav_viewed=True, group_metadata=True) if data is None: return {"errors": ["session not found"]} - if not data.get("live"): - sessions_favorite_viewed.view_session(project_id=projectId, user_id=context.user_id, session_id=sessionId) + sessions_favorite_viewed.view_session(project_id=projectId, user_id=context.user_id, session_id=sessionId) return { 'data': data } diff --git a/backend/pkg/db/postgres/connector.go b/backend/pkg/db/postgres/connector.go index 9e4e82633..1eb29e04c 100644 --- a/backend/pkg/db/postgres/connector.go +++ b/backend/pkg/db/postgres/connector.go @@ -10,7 +10,7 @@ import ( ) func getTimeoutContext() context.Context { - ctx, _ := context.WithTimeout(context.Background(), time.Duration(time.Second*10)) + ctx, _ := context.WithTimeout(context.Background(), time.Duration(time.Second*30)) return ctx } diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index c8c1c7669..9ca77c1ea 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -156,7 +156,7 @@ def update(tenant_id, user_id, changes): (SELECT role_id FROM roles WHERE tenant_id = %(tenant_id)s AND name != 'Owner' LIMIT 1)))""") else: sub_query_users.append(f"{helper.key_to_snake_case(key)} = %({key})s") - + changes["role_id"] = changes.get("roleId", changes.get("role_id")) with pg_client.PostgresClient() as cur: if len(sub_query_users) > 0: cur.execute( diff --git a/frontend/app/Router.js b/frontend/app/Router.js index 86f07c4f6..782f47e74 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -21,6 +21,10 @@ import Header from 'Components/Header/Header'; import FunnelDetails from 'Components/Funnels/FunnelDetails'; import FunnelIssueDetails from 'Components/Funnels/FunnelIssueDetails'; import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; +import { fetchList as fetchSiteList } from 'Duck/site'; +import { fetchList as fetchAnnouncements } from 'Duck/announcements'; +import { fetchList as fetchAlerts } from 'Duck/alerts'; +import { fetchWatchdogStatus } from 'Duck/watchdogs'; import APIClient from './api_client'; import * as routes from './routes'; @@ -80,7 +84,14 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); onboarding: state.getIn([ 'user', 'onboarding' ]) }; }, { - fetchUserInfo, fetchTenants, setSessionPath, fetchIntegrationVariables + fetchUserInfo, + fetchTenants, + setSessionPath, + fetchIntegrationVariables, + fetchSiteList, + fetchAnnouncements, + fetchAlerts, + fetchWatchdogStatus, }) class Router extends React.Component { state = { @@ -93,6 +104,14 @@ class Router extends React.Component { props.fetchUserInfo().then(() => { props.fetchIntegrationVariables() }), + props.fetchSiteList().then(() => { + setTimeout(() => { + props.fetchAnnouncements(); + props.fetchAlerts(); + props.fetchWatchdogStatus(); + }, 100); + }), + // props.fetchAnnouncements(), ]) // .then(() => this.onLoginLogout()); } diff --git a/frontend/app/components/Alerts/Notifications/Notifications.js b/frontend/app/components/Alerts/Notifications/Notifications.js index deb14b640..a30ad824f 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.js +++ b/frontend/app/components/Alerts/Notifications/Notifications.js @@ -18,9 +18,9 @@ class Notifications extends React.Component { constructor(props) { super(props); - setTimeout(() => { - props.fetchList(); - }, 1000); + // setTimeout(() => { + // props.fetchList(); + // }, 1000); setInterval(() => { props.fetchList(); diff --git a/frontend/app/components/Announcements/Announcements.js b/frontend/app/components/Announcements/Announcements.js index ff392e2e7..733b4ce37 100644 --- a/frontend/app/components/Announcements/Announcements.js +++ b/frontend/app/components/Announcements/Announcements.js @@ -10,11 +10,7 @@ import { withRouter } from 'react-router-dom'; @withToggle('visible', 'toggleVisisble') @withRouter class Announcements extends React.Component { - constructor(props) { - super(props); - props.fetchList(); - } - + navigateToUrl = url => { if (url) { if (url.startsWith(window.ENV.ORIGIN)) { diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index f0024c20a..6d30359f1 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -4,14 +4,11 @@ import withPageTitle from 'HOCs/withPageTitle'; import { fetchFavoriteList as fetchFavoriteSessionList } from 'Duck/sessions'; -import { countries } from 'App/constants'; import { applyFilter, clearEvents, addAttribute } from 'Duck/filters'; import { fetchList as fetchFunnelsList } from 'Duck/funnels'; -import { defaultFilters, preloadedFilters } from 'Types/filter'; import { KEYS } from 'Types/filter/customFilter'; import SessionList from './SessionList'; import stl from './bugFinder.css'; -import { fetchList as fetchSiteList } from 'Duck/site'; import withLocationHandlers from "HOCs/withLocationHandlers"; import { fetch as fetchFilterVariables } from 'Duck/sources'; import { fetchSources } from 'Duck/customField'; @@ -68,7 +65,6 @@ const allowedQueryKeys = [ fetchSources, clearEvents, setActiveTab, - fetchSiteList, fetchFunnelsList, resetFunnel, resetFunnelFilters, @@ -81,7 +77,6 @@ export default class BugFinder extends React.PureComponent { state = {showRehydratePanel: false} constructor(props) { super(props); - // props.fetchFavoriteSessionList(); // TODO should cache the response // props.fetchSources().then(() => { @@ -115,29 +110,6 @@ export default class BugFinder extends React.PureComponent { this.setState({ showRehydratePanel: !this.state.showRehydratePanel }) } - // fetchPreloadedFilters = () => { - // this.props.fetchFilterVariables('filterValues').then(function() { - // const { filterValues } = this.props; - // const keys = [ - // {key: KEYS.USER_OS, label: 'OS'}, - // {key: KEYS.USER_BROWSER, label: 'Browser'}, - // {key: KEYS.USER_DEVICE, label: 'Device'}, - // {key: KEYS.REFERRER, label: 'Referrer'}, - // {key: KEYS.USER_COUNTRY, label: 'Country'}, - // ] - // if (filterValues && filterValues.size != 0) { - // keys.forEach(({key, label}) => { - // const _keyFilters = filterValues.get(key) - // if (key === KEYS.USER_COUNTRY) { - // preloadedFilters.push(_keyFilters.map(item => ({label, type: key, key, value: item, actualValue: countries[item], isFilter: true}))); - // } else { - // preloadedFilters.push(_keyFilters.map(item => ({label, type: key, key, value: item, isFilter: true}))); - // } - // }) - // } - // }.bind(this)); - // } - setActiveTab = tab => { this.props.setActiveTab(tab); } diff --git a/frontend/app/components/BugFinder/DateRange.js b/frontend/app/components/BugFinder/DateRange.js index ed8c76c98..c0758b426 100644 --- a/frontend/app/components/BugFinder/DateRange.js +++ b/frontend/app/components/BugFinder/DateRange.js @@ -10,7 +10,7 @@ import DateRangeDropdown from 'Shared/DateRangeDropdown'; }) export default class DateRange extends React.PureComponent { onDateChange = (e) => { - this.props.fetchFunnelsList(e.rangeValue) + // this.props.fetchFunnelsList(e.rangeValue) this.props.applyFilter(e) } render() { diff --git a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js index 2436d4be6..be98a28cd 100644 --- a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js +++ b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js @@ -22,9 +22,9 @@ function SessionsMenu(props) { } } - useEffect(() => { - fetchWatchdogStatus() - }, []) + // useEffect(() => { + // fetchWatchdogStatus() + // }, []) const capturingAll = props.captureRate && props.captureRate.get('captureAll'); diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index 6cae36710..cb55d933d 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -3,7 +3,6 @@ import { withRouter } from 'react-router-dom'; import { Switch, Route, Redirect } from 'react-router'; import { CLIENT_TABS, client as clientRoute } from 'App/routes'; import { fetchList as fetchMemberList } from 'Duck/member'; -import { fetchList as fetchSiteList } from 'Duck/site'; import ProfileSettings from './ProfileSettings'; import Integrations from './Integrations'; @@ -21,7 +20,6 @@ import Roles from './Roles'; appearance: state.getIn([ 'user', 'account', 'appearance' ]), }), { fetchMemberList, - fetchSiteList, }) @withRouter export default class Client extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Dashboard.js b/frontend/app/components/Dashboard/Dashboard.js index aacda8b07..8ad8ecdf8 100644 --- a/frontend/app/components/Dashboard/Dashboard.js +++ b/frontend/app/components/Dashboard/Dashboard.js @@ -240,7 +240,6 @@ export default class Dashboard extends React.PureComponent { Custom Metrics are not supported for comparison. )} - {/* */} } > diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx new file mode 100644 index 000000000..ffbbc6b88 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import { Styles } from '../../common'; +import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; +import { LineChart, Line, Legend } from 'recharts'; + +interface Props { + data: any; + params: any; + seriesMap: any; + colors: any; + onClick?: (event, index) => void; +} +function CustomMetriLineChart(props: Props) { + const { data, params, seriesMap, colors, onClick = () => null } = props; + return ( + + + + + + + + { seriesMap.map((key, index) => ( + + ))} + + + ) +} + +export default CustomMetriLineChart diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts new file mode 100644 index 000000000..f05a16274 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetriLineChart'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx new file mode 100644 index 000000000..cfd0d5295 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/CustomMetricPercentage.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +interface Props { + data: any; + params: any; + colors: any; + onClick?: (event, index) => void; +} +function CustomMetriPercentage(props: Props) { + const { data = {} } = props; + return ( +
+
{data.count}
+
{`${data.previousCount} ( ${data.countProgress}% ) from previous period.`}
+
+ ) +} + +export default CustomMetriPercentage; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/index.ts new file mode 100644 index 000000000..0e1f80170 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricPercentage'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.css new file mode 100644 index 000000000..1d1ef3ee4 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.css @@ -0,0 +1,6 @@ +.wrapper { + background-color: white; + /* border: solid thin $gray-medium; */ + border-radius: 3px; + padding: 10px; +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx new file mode 100644 index 000000000..99c054fae --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -0,0 +1,155 @@ +import React from 'react' +import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; +import { LineChart, Line, Legend, PieChart, Pie, Cell } from 'recharts'; +import { Styles } from '../../common'; + + +function renderCustomizedLabel({ + cx, cy, midAngle, innerRadius, outerRadius, value, color, startAngle, endAngle}) { + const RADIAN = Math.PI / 180; + const diffAngle = endAngle - startAngle; + const delta = ((360-diffAngle)/15)-1; + const radius = innerRadius + (outerRadius - innerRadius); + const x = cx + (radius+delta) * Math.cos(-midAngle * RADIAN); + const y = cy + (radius+(delta*delta)) * Math.sin(-midAngle * RADIAN); + return ( + cx ? 'start' : 'end'} dominantBaseline="central" fontSize={12} fontWeight="normal"> + {value} + + ); +}; +function renderCustomizedLabelLine(props){ + let { cx, cy, midAngle, innerRadius, outerRadius, color, startAngle, endAngle } = props; + const RADIAN = Math.PI / 180; + const diffAngle = endAngle - startAngle; + const radius = 10 + innerRadius + (outerRadius - innerRadius); + let path=''; + for(let i=0;i<((360-diffAngle)/15);i++){ + path += `${(cx + (radius+i) * Math.cos(-midAngle * RADIAN))},${(cy + (radius+i*i) * Math.sin(-midAngle * RADIAN))} ` + } + return ( + + ); +} +interface Props { + data: any; + params: any; + // seriesMap: any; + colors: any; + onClick?: (event, index) => void; +} + +function CustomMetricPieChart(props: Props) { + const { data = { values: [] }, params, colors, onClick = () => null } = props; + return ( + + + { + const RADIAN = Math.PI / 180; + let radius1 = 15 + innerRadius + (outerRadius - innerRadius); + let radius2 = innerRadius + (outerRadius - innerRadius); + let x2 = cx + radius1 * Math.cos(-midAngle * RADIAN); + let y2 = cy + radius1 * Math.sin(-midAngle * RADIAN); + let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN); + let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN); + + const percentage = value * 100 / data.values.reduce((a, b) => a + b.sessionCount, 0); + + if (percentage<3){ + return null; + } + + return( + + ) + }} + label={({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + value, + index + }) => { + const RADIAN = Math.PI / 180; + let radius = 20 + innerRadius + (outerRadius - innerRadius); + let x = cx + radius * Math.cos(-midAngle * RADIAN); + let y = cy + radius * Math.sin(-midAngle * RADIAN); + const percentage = (value / data.values.reduce((a, b) => a + b.sessionCount, 0)) * 100; + if (percentage<3){ + return null; + } + return ( + cx ? "start" : "end"} + dominantBaseline="central" + fill='#3EAAAF' + > + {data.values[index].name} - ({value}) + + ); + }} + // label={({ + // cx, + // cy, + // midAngle, + // innerRadius, + // outerRadius, + // value, + // index + // }) => { + // const RADIAN = Math.PI / 180; + // const radius = 30 + innerRadius + (outerRadius - innerRadius); + // const x = cx + radius * Math.cos(-midAngle * RADIAN); + // const y = cy + radius * Math.sin(-midAngle * RADIAN); + + // return ( + // cx ? "start" : "end"} + // dominantBaseline="top" + // fontSize={10} + // > + // {data.values[index].name} ({value}) + // + // ); + // }} + > + {data.values.map((entry, index) => ( + + ))} + + + + + ) +} + +export default CustomMetricPieChart; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/index.ts new file mode 100644 index 000000000..6bdaf2270 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricPieChart'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.css new file mode 100644 index 000000000..1d1ef3ee4 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.css @@ -0,0 +1,6 @@ +.wrapper { + background-color: white; + /* border: solid thin $gray-medium; */ + border-radius: 3px; + padding: 10px; +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx new file mode 100644 index 000000000..be94acc57 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { Table } from '../../common'; +import { List } from 'immutable'; + +const cols = [ + { + key: 'name', + title: 'Resource', + toText: name => name || 'Unidentified', + width: '70%', + }, + { + key: 'sessionCount', + title: 'Sessions', + toText: sessions => sessions, + width: '30%', + }, +]; + +interface Props { + data: any; + onClick?: (event, index) => void; +} +function CustomMetriTable(props: Props) { + const { data = { values: [] }, onClick = () => null } = props; + const rows = List(data.values); + return ( +
+ + + ) +} + +export default CustomMetriTable; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/index.ts new file mode 100644 index 000000000..dc43c93b4 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricTable'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx index bbf37408a..f473ed270 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx @@ -2,22 +2,25 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { Loader, NoContent, Icon, Popup } from 'UI'; import { Styles } from '../../common'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LineChart, Line, Legend } from 'recharts'; +import { ResponsiveContainer } from 'recharts'; import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; import stl from './CustomMetricWidget.css'; import { getChartFormatter, getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; import { init, edit, remove, setAlertMetricId, setActiveWidget, updateActiveState } from 'Duck/customMetrics'; import APIClient from 'App/api_client'; import { setShowAlerts } from 'Duck/dashboard'; +import CustomMetriLineChart from '../CustomMetriLineChart'; +import CustomMetricPieChart from '../CustomMetricPieChart'; +import CustomMetricPercentage from '../CustomMetricPercentage'; +import CustomMetricTable from '../CustomMetricTable'; const customParams = rangeName => { const params = { density: 70 } - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 + // if (rangeName === LAST_24_HOURS) params.density = 70 + // if (rangeName === LAST_30_MINUTES) params.density = 70 + // if (rangeName === YESTERDAY) params.density = 70 + // if (rangeName === LAST_7_DAYS) params.density = 70 return params } @@ -47,11 +50,10 @@ function CustomMetricWidget(props: Props) { const colors = Styles.customMetricColors; const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart', startDate: period.start, endDate: period.end } useEffect(() => { - new APIClient()['post']('/custom_metrics/chart', { ...metricParams, q: metric.name }) + new APIClient()['post'](`/custom_metrics/${metricParams.metricId}/chart`, { ...metricParams, q: metric.name }) .then(response => response.json()) .then(({ errors, data }) => { if (errors) { @@ -78,8 +80,19 @@ function CustomMetricWidget(props: Props) { if (event) { const payload = event.activePayload[0].payload; const timestamp = payload.timestamp; - const { startTimestamp, endTimestamp } = getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density); - props.setActiveWidget({ widget: metric, startTimestamp, endTimestamp, timestamp: payload.timestamp, index }) + const periodTimestamps = metric.metricType === 'timeseries' ? + getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density) : + period.toTimestamps(); + + const activeWidget = { + widget: metric, + period: period, + ...periodTimestamps, + timestamp: payload.timestamp, + index, + } + + props.setActiveWidget(activeWidget); } } @@ -89,7 +102,7 @@ function CustomMetricWidget(props: Props) { return (
-
+
{metric.name}
@@ -97,56 +110,51 @@ function CustomMetricWidget(props: Props) { updateActiveState(metric.metricId, false)} />
-
+
- - - - - - - - - - - - - { seriesMap.map((key, index) => ( - + {metric.viewType === 'lineChart' && ( + - ))} - + )} + + {metric.viewType === 'pieChart' && ( + + )} + + {metric.viewType === 'progress' && ( + + )} + + {metric.viewType === 'table' && ( + + )} + diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css index 1d1ef3ee4..42444d934 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css @@ -1,6 +1,13 @@ .wrapper { - background-color: white; + background-color: $gray-light; /* border: solid thin $gray-medium; */ border-radius: 3px; - padding: 10px; + padding: 20px; +} + +.innerWapper { + border-radius: 3px; + width: 70%; + margin: 0 auto; + background-color: white; } \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx index 8af9784d5..eb37268fc 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx @@ -1,16 +1,20 @@ import React, { useEffect, useState, useRef } from 'react'; import { connect } from 'react-redux'; -import { Loader, NoContent, Icon } from 'UI'; +import { Loader, NoContent, SegmentSelection, Icon } from 'UI'; import { Styles } from '../../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts'; +// import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts'; import Period, { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; import stl from './CustomMetricWidgetPreview.css'; import { getChartFormatter } from 'Types/dashboard/helper'; import { remove } from 'Duck/customMetrics'; import DateRange from 'Shared/DateRange'; import { edit } from 'Duck/customMetrics'; +import CustomMetriLineChart from '../CustomMetriLineChart'; +import CustomMetricPercentage from '../CustomMetricPercentage'; +import CustomMetricTable from '../CustomMetricTable'; import APIClient from 'App/api_client'; +import CustomMetricPieChart from '../CustomMetricPieChart'; const customParams = rangeName => { const params = { density: 70 } @@ -43,8 +47,9 @@ function CustomMetricWidget(props: Props) { const params = customParams(period.rangeName) const gradientDef = Styles.gradientDef(); const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart' } - const prevMetricRef = useRef(); + const isTimeSeries = metric.metricType === 'timeseries'; + const isTable = metric.metricType === 'table'; useEffect(() => { // Check for title change @@ -83,11 +88,46 @@ function CustomMetricWidget(props: Props) { props.edit({ ...changedDates, rangeName: changedDates.rangeValue }); } + const chagneViewType = (e, { name, value }) => { + props.edit({ [ name ]: value }); + } + return (
Preview
-
+
+ {isTimeSeries && ( + + )} + + {isTable && ( + + )} +
+ Time Range
-
+
- - - - - - - - { seriesMap.map((key, index) => ( - - ))} - - +
+ {metric.name} +
+
+ { isTimeSeries && ( + <> + { metric.viewType === 'progress' && ( + + )} + { metric.viewType === 'lineChart' && ( + + )} + + )} + + { isTable && ( + <> + { metric.viewType === 'table' ? ( + + ) : ( + + )} + + )} +
diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx index 6cf633518..57c181904 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricsWidgets.tsx @@ -5,6 +5,7 @@ import CustomMetricWidget from './CustomMetricWidget'; import AlertFormModal from 'App/components/Alerts/AlertFormModal'; import { init as initAlert } from 'Duck/alerts'; import LazyLoad from 'react-lazyload'; +import CustomMetrics from 'App/components/shared/CustomMetrics'; interface Props { fetchList: Function; @@ -22,7 +23,7 @@ function CustomMetricsWidgets(props: Props) { return ( <> - {list.filter(item => item.active).map((item: any) => ( + {list.map((item: any) => ( ))} + {list.size === 0 && ( +
+
Be proactive by monitoring the metrics you care about the most.
+ +
+ )} + ({ - list: state.getIn(['customMetrics', 'list']), + list: state.getIn(['customMetrics', 'list']).filter(item => item.active), }), { fetchList, initAlert })(CustomMetricsWidgets); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/Styles.js b/frontend/app/components/Dashboard/Widgets/common/Styles.js index 7b763f698..f071127dd 100644 --- a/frontend/app/components/Dashboard/Widgets/common/Styles.js +++ b/frontend/app/components/Dashboard/Widgets/common/Styles.js @@ -5,6 +5,7 @@ const colorsx = ['#256669', '#38999e', '#3eaaaf', '#51b3b7', '#78c4c7', '#9fd5d7 const compareColors = ['#394EFF', '#4D5FFF', '#808DFF', '#B3BBFF', '#E5E8FF']; const compareColorsx = ["#222F99", "#2E3ECC", "#394EFF", "#6171FF", "#8895FF", "#B0B8FF", "#D7DCFF"].reverse(); const customMetricColors = ['#3EAAAF', '#394EFF', '#666666']; +const colorsPie = colors.concat(["#DDDDDD"]); const countView = count => { const isMoreThanK = count >= 1000; @@ -14,6 +15,7 @@ const countView = count => { export default { customMetricColors, colors, + colorsPie, colorsx, compareColors, compareColorsx, diff --git a/frontend/app/components/Dashboard/Widgets/common/Table.js b/frontend/app/components/Dashboard/Widgets/common/Table.js index 0aecca5ea..8fcef4315 100644 --- a/frontend/app/components/Dashboard/Widgets/common/Table.js +++ b/frontend/app/components/Dashboard/Widgets/common/Table.js @@ -16,7 +16,8 @@ export default class Table extends React.PureComponent { rowProps, rowClass = '', small = false, - compare = false + compare = false, + maxHeight = 200, } = this.props; const { showAll } = this.state; @@ -30,7 +31,7 @@ export default class Table extends React.PureComponent {
{ title }
) }
-
+
{ rows.take(showAll ? 10 : (small ? 3 : 5)).map(row => (
{ cols.map(({ cellClass = '', className = '', Component, key, toText = t => t, width }) => ( @@ -41,21 +42,20 @@ export default class Table extends React.PureComponent {
)) }
- )) } - - { rows.size > (small ? 3 : 5) && !showAll && -
+ )) } +
+ { rows.size > (small ? 3 : 5) && !showAll && +
} -
); } diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 38d958c17..6d541fca7 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -66,10 +66,6 @@ const Header = (props) => { } }, [showTrackingModal]) - useEffect(() => { - fetchSiteList() - }, []) - return (
diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 55259c088..74b650055 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -11,6 +11,8 @@ import cn from 'classnames'; import NewSiteForm from '../Client/Sites/NewSiteForm'; import { clearSearch } from 'Duck/search'; import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; +import { fetchList as fetchAlerts } from 'Duck/alerts'; +import { fetchWatchdogStatus } from 'Duck/watchdogs'; @withRouter @connect(state => ({ @@ -23,13 +25,15 @@ import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; init, clearSearch, fetchIntegrationVariables, + fetchAlerts, + fetchWatchdogStatus, }) export default class SiteDropdown extends React.PureComponent { state = { showProductModal: false } - componentDidMount() { - this.props.fetchIntegrationVariables(); - } + // componentDidMount() { + // this.props.fetchIntegrationVariables(); + // } closeModal = (e, newSite) => { this.setState({ showProductModal: false }) @@ -44,6 +48,8 @@ export default class SiteDropdown extends React.PureComponent { this.props.setSiteId(siteId); this.props.clearSearch(); this.props.fetchIntegrationVariables(); + this.props.fetchAlerts(); + this.props.fetchWatchdogStatus(); } render() { diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js index 0138e7e50..210a24bff 100644 --- a/frontend/app/components/Session/Session.js +++ b/frontend/app/components/Session/Session.js @@ -16,11 +16,11 @@ const SESSIONS_ROUTE = sessionsRoute(); function Session({ sessionId, loading, - hasErrors, + hasErrors, session, fetchSession, - fetchSlackList, - hasSessionsPath + fetchSlackList, + hasSessionsPath }) { usePageTitle("OpenReplay Session Player"); useEffect(() => { @@ -51,7 +51,7 @@ function Session({ { session.isIOS ? - : (session.live && !hasSessionsPath ? : ) + : } diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js index b0dca0956..0992c0f88 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js @@ -10,13 +10,15 @@ export default connect(state => ({ metadata: state.getIn([ 'sessions', 'current', 'metadata' ]), }))(function Metadata ({ metadata }) { const [ visible, setVisible ] = useState(false); - const toggle = useCallback(() => metadata.length > 0 && setVisible(v => !v), []); + const metaLenth = Object.keys(metadata).length; + const toggle = useCallback(() => metaLenth > 0 && setVisible(v => !v), []); + return ( <> ({
} on="click" - disabled={metadata.length > 0} + disabled={metaLenth > 0} size="tiny" inverted position="top center" /> { visible &&
- - { metadata.map((i) => { - const key = Object.keys(i)[0] - const value = i[key] + + { Object.keys(metadata).map((key) => { + // const key = Object.keys(i)[0] + const value = metadata[key] return }) } diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js b/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js index 8abd1913c..76cf459b0 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/MetadataItem.js @@ -49,7 +49,7 @@ export default class extends React.PureComponent { content={ open && } onClose={ open ? this.switchOpen : () => null } /> -
+
{ item.key }
{ } const NodesCountTooltip = ({ active, payload} ) => { - if (!active || payload.length === 0) return null; + if (!active || !payload || payload.length === 0) return null; return (

diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx index 7700a7a29..ea923a6e1 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Form, SegmentSelection, Button, IconButton } from 'UI'; +import { Form, Button, IconButton } from 'UI'; import FilterSeries from '../FilterSeries'; import { connect } from 'react-redux'; import { edit as editMetric, save, addSeries, removeSeries, remove } from 'Duck/customMetrics'; @@ -7,7 +7,8 @@ import CustomMetricWidgetPreview from 'App/components/Dashboard/Widgets/CustomMe import { confirm } from 'UI/Confirmation'; import { toast } from 'react-toastify'; import cn from 'classnames'; - +import DropdownPlain from '../../DropdownPlain'; +import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions'; interface Props { metric: any; editMetric: (metric, shouldFetch?) => void; @@ -21,6 +22,13 @@ interface Props { function CustomMetricForm(props: Props) { const { metric, loading } = props; + // const metricOfOptions = metricOf.filter(i => i.key === metric.metricType); + const timeseriesOptions = metricOf.filter(i => i.key === 'timeseries'); + const tableOptions = metricOf.filter(i => i.key === 'table'); + const isTable = metric.metricType === 'table'; + const isTimeSeries = metric.metricType === 'timeseries'; + const _issueOptions = [{ text: 'All', value: '' }].concat(issueOptions); + const addSeries = () => { props.addSeries(); @@ -30,12 +38,33 @@ function CustomMetricForm(props: Props) { props.removeSeries(index); } - const write = ({ target: { value, name } }) => props.editMetric({ ...metric, [ name ]: value }, false); + const write = ({ target: { value, name } }) => props.editMetric({ [ name ]: value }, false); + const writeOption = (e, { value, name }) => { + props.editMetric({ [ name ]: value }, false); - const changeConditionTab = (e, { name, value }) => { - props.editMetric({[ 'viewType' ]: value }); + if (name === 'metricValue') { + props.editMetric({ metricValue: [value] }, false); + } + + if (name === 'metricOf') { + if (value === 'ISSUES') { + props.editMetric({ metricValue: [''] }, false); + } + } + + if (name === 'metricType') { + if (value === 'timeseries') { + props.editMetric({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' }, false); + } else if (value === 'table') { + props.editMetric({ metricOf: tableOptions[0].value, viewType: 'table' }, false); + } + } }; + // const changeConditionTab = (e, { name, value }) => { + // props.editMetric({[ 'viewType' ]: value }); + // }; + const save = () => { props.save(metric).then(() => { toast.success(metric.exists() ? 'Updated succesfully.' : 'Created succesfully.'); @@ -79,30 +108,71 @@ function CustomMetricForm(props: Props) {

- Timeseries - of -
- -
+ + + {metric.metricType === 'timeseries' && ( + <> + of + + + )} + + {metric.metricType === 'table' && ( + <> + of + + + )} + + {metric.metricOf === 'ISSUES' && ( + <> + issue type + + + )} + + {metric.metricType === 'table' && ( + <> + showing + + + )}
- {metric.series && metric.series.size > 0 && metric.series.map((series: any, index: number) => ( + {metric.series && metric.series.size > 0 && metric.series.take(isTable ? 1 : metric.series.size).map((series: any, index: number) => (
removeSeries(index)} @@ -112,9 +182,11 @@ function CustomMetricForm(props: Props) { ))}
-
2})}> - -
+ { isTimeSeries && ( +
2})}> + +
+ )}
diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx index aedd4a097..ae0718aea 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { IconButton } from 'UI'; import { connect } from 'react-redux'; import { edit, init } from 'Duck/customMetrics'; @@ -7,6 +7,9 @@ interface Props { init: (instance?, setDefault?) => void; } function CustomMetrics(props: Props) { + useEffect(() => { // TODO remove this block + props.init() + }, []) return (
props.init()} /> diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx index 0f5df220b..aea20aea1 100644 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx @@ -25,10 +25,12 @@ interface Props { editSeriesFilterFilter: typeof editSeriesFilterFilter; editSeriesFilter: typeof editSeriesFilter; removeSeriesFilterFilter: typeof removeSeriesFilterFilter; + hideHeader?: boolean; + emptyMessage?: any; } function FilterSeries(props: Props) { - const { canDelete } = props; + const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props; const [expanded, setExpanded] = useState(true) const { series, seriesIndex } = props; @@ -51,7 +53,7 @@ function FilterSeries(props: Props) { return (
-
+
props.updateSeries(seriesIndex, { name }) } />
@@ -78,10 +80,10 @@ function FilterSeries(props: Props) { onChangeEventsOrder={onChangeEventsOrder} /> ): ( -
Add user event or filter to define the series by clicking Add Step.
+
{emptyMessage}
)}
-
+
setActiveSeries(value); const filteredSessions = getListSessionsBySeries(activeSeries); - const startTime = DateTime.fromMillis(activeWidget.startTimestamp).toFormat('LLL dd, yyyy HH:mm a'); const endTime = DateTime.fromMillis(activeWidget.endTimestamp).toFormat('LLL dd, yyyy HH:mm a'); + return ( void; icon?: string; direction?: string; value: any; + multiple?: boolean; } export default function DropdownPlain(props: Props) { - const { value, options, icon = "chevron-down", direction = "left" } = props; + const { name = "sort", value, options, icon = "chevron-down", direction = "right", multiple = false } = props; return (
: null } /> diff --git a/frontend/app/components/shared/FilterDropdown/FilterDropdown.js b/frontend/app/components/shared/FilterDropdown/FilterDropdown.js index 66da1f586..5b8a4ac76 100644 --- a/frontend/app/components/shared/FilterDropdown/FilterDropdown.js +++ b/frontend/app/components/shared/FilterDropdown/FilterDropdown.js @@ -100,18 +100,20 @@ const FilterDropdown = props => {
)} {showDropdown && ( -
-
SELECT FILTER
- {filterKeys.filter(f => !filterKeyMaps.includes(f.key)).map(f => ( -
onFilterKeySelect(f.key)} - className={cn(stl.filterItem, 'py-3 -mx-3 px-3 flex items-center cursor-pointer')} - > - - {f.name} -
- ))} +
+
SELECT FILTER
+
+ {filterKeys.filter(f => !filterKeyMaps.includes(f.key)).map(f => ( +
onFilterKeySelect(f.key)} + className={cn(stl.filterItem, 'py-3 -mx-3 px-3 flex items-center cursor-pointer')} + > + + {f.name} +
+ ))} +
)} {filterKey && ( diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index 333188b3e..92baa3d51 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -47,15 +47,17 @@ function FilterAutoComplete(props: Props) { const requestValues = (q) => { setLoading(true); - return new APIClient()[method?.toLowerCase()](endpoint, { ...params, q }) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - // this.setError(); - } else { - setOptions(data); - } - }).finally(() => setLoading(false)); + return new APIClient()[method?.toLocaleLowerCase()](endpoint, { ...params, q }) + .then(response => { + if (response.ok) { + return response.json(); + } + throw new Error(response.statusText); + }) + .then(({ data }) => { + setOptions(data); + }) + .finally(() => setLoading(false)); } const debouncedRequestValues = React.useCallback(debounce(requestValues, 300), []); diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index db0bedf32..f01fefcd8 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -4,6 +4,8 @@ import FilterSelection from '../FilterSelection'; import FilterValue from '../FilterValue'; import { Icon } from 'UI'; import FilterSource from '../FilterSource'; +import { FilterType } from 'App/types/filter/filterType'; +import SubFilterItem from '../SubFilterItem'; interface Props { filterIndex: number; @@ -15,9 +17,14 @@ interface Props { function FilterItem(props: Props) { const { isFilter = false, filterIndex, filter } = props; const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined"); + const isSubFilter = filter.type === FilterType.SUB_FILTERS; const replaceFilter = (filter) => { - props.onUpdate({ ...filter, value: [""]}); + props.onUpdate({ + ...filter, + value: [""], + subFilters: filter.subFilters ? filter.subFilters.map(i => ({ ...i, value: [""] })) : [] + }); }; const onOperatorChange = (e, { name, value }) => { @@ -28,6 +35,19 @@ function FilterItem(props: Props) { props.onUpdate({ ...filter, sourceOperator: value }) } + const onUpdateSubFilter = (subFilter, subFilterIndex) => { + props.onUpdate({ + ...filter, + subFilters: filter.subFilters.map((i, index) => { + if (index === subFilterIndex) { + return subFilter; + } + return i; + }) + }); + }; + + return (
@@ -48,14 +68,31 @@ function FilterItem(props: Props) { )} {/* Filter values */} - - { canShowValues && () } - + { !isSubFilter && ( + <> + + { canShowValues && () } + + )} + + {/* SubFilters */} + {isSubFilter && ( +
+ {filter.subFilters.map((subFilter, subFilterIndex) => ( + onUpdateSubFilter(f, subFilterIndex)} + onRemoveFilter={props.onRemoveFilter} + /> + ))} +
+ )}
Events Order
} - content={ `Events Order` } + content={ `Select the operator to be applied between events in your search.` } size="tiny" inverted position="top center" diff --git a/frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx b/frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx new file mode 100644 index 000000000..73c8663b9 --- /dev/null +++ b/frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx @@ -0,0 +1,34 @@ +import { filter } from 'App/components/BugFinder/ManageFilters/savedFilterList.css' +import React from 'react' +import FilterOperator from '../FilterOperator'; +import FilterValue from '../FilterValue'; + +interface Props { + filterIndex: number; + filter: any; // event/filter + onUpdate: (filter) => void; + onRemoveFilter: () => void; + isFilter?: boolean; +} +export default function SubFilterItem(props: Props) { + const { isFilter = false, filterIndex, filter } = props; + const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined"); + + const onOperatorChange = (e, { name, value }) => { + props.onUpdate({ ...filter, operator: value }) + } + + return ( +
+
{filter.label}
+ + + { canShowValues && () } +
+ ) +} diff --git a/frontend/app/components/shared/Filters/SubFilterItem/index.ts b/frontend/app/components/shared/Filters/SubFilterItem/index.ts new file mode 100644 index 000000000..0877700cc --- /dev/null +++ b/frontend/app/components/shared/Filters/SubFilterItem/index.ts @@ -0,0 +1 @@ +export { default } from './SubFilterItem'; \ No newline at end of file diff --git a/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx b/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx index 809eb739f..19a3b7ceb 100644 --- a/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx +++ b/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx @@ -18,12 +18,6 @@ function FunnelSearch(props: Props) { const onAddFilter = (filter) => { props.addFilter(filter); - // filter.value = [""] - // const newFilters = appliedFilter.filters.concat(filter); - // props.edit({ - // ...appliedFilter.filter, - // filters: newFilters, - // }); } const onUpdateFilter = (filterIndex, filter) => { diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 264786fff..17904c1ba 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -19,12 +19,6 @@ function SessionSearch(props: Props) { const onAddFilter = (filter) => { props.addFilter(filter); - // filter.value = [""] - // const newFilters = appliedFilter.filters.concat(filter); - // props.edit({ - // ...appliedFilter.filter, - // filters: newFilters, - // }); } const onUpdateFilter = (filterIndex, filter) => { diff --git a/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js b/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js index a5fe4411c..787751d79 100644 --- a/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js +++ b/frontend/app/components/shared/TrackerUpdateMessage/TrackerUpdateMessage.js @@ -1,18 +1,25 @@ -import React from 'react' +import React, { useEffect } from 'react' import { Icon } from 'UI' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom'; import { onboarding as onboardingRoute } from 'App/routes' import { withSiteId } from 'App/routes'; +import { isGreaterOrEqualVersion } from 'App/utils' const TrackerUpdateMessage= (props) => { - // const { site } = props; - const { site, sites, match: { params: { siteId } } } = props; + const [needUpdate, setNeedUpdate] = React.useState(false) + const { sites, match: { params: { siteId } } } = props; const activeSite = sites.find(s => s.id == siteId); - const hasSessions = !!activeSite && !activeSite.recorded; - const appVersionInt = parseInt(window.ENV.TRACKER_VERSION.split(".").join("")) - const trackerVersionInt = site.trackerVersion ? parseInt(site.trackerVersion.split(".").join("")) : 0 - const needUpdate = !hasSessions && appVersionInt > trackerVersionInt; + + useEffect(() => { + if (!activeSite || !activeSite.trackerVersion) return; + + const isLatest = isGreaterOrEqualVersion(activeSite.trackerVersion, window.ENV.TRACKER_VERSION); + if (!isLatest && activeSite.recorded) { + setNeedUpdate(true) + } + }, [activeSite]) + return needUpdate ? ( <> {( diff --git a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js index a862394ee..74335fddd 100644 --- a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js +++ b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js @@ -9,13 +9,14 @@ class SegmentSelection extends React.Component { } render() { - const { className, list, small = false, extraSmall = false, primary = false, size = "normal" } = this.props; + const { className, list, small = false, extraSmall = false, primary = false, size = "normal", icons = false } = this.props; return (
{ list.map(item => ( @@ -27,8 +28,8 @@ class SegmentSelection extends React.Component { data-active={ this.props.value && this.props.value.value === item.value } onClick={ () => !item.disabled && this.setActiveItem(item) } > - { item.icon && } -
{ item.name }
+ { item.icon && } +
{ item.name }
} disabled={!item.disabled} diff --git a/frontend/app/components/ui/SegmentSelection/segmentSelection.css b/frontend/app/components/ui/SegmentSelection/segmentSelection.css index 20007b010..907f81e37 100644 --- a/frontend/app/components/ui/SegmentSelection/segmentSelection.css +++ b/frontend/app/components/ui/SegmentSelection/segmentSelection.css @@ -12,13 +12,13 @@ padding: 10px; flex: 1; text-align: center; - border-right: solid thin $teal; cursor: pointer; background-color: $gray-lightest; display: flex; align-items: center; justify-content: center; white-space: nowrap; + border-right: solid thin $gray-light; & span svg { fill: $gray-medium; @@ -53,9 +53,16 @@ & .item { color: $teal; background-color: white; + border-right: solid thin $teal; + & svg { + fill: $teal !important; + } &[data-active=true] { background-color: $teal; color: white; + & svg { + fill: white !important; + } } } } @@ -65,6 +72,11 @@ } .extraSmall .item { - padding: 0 4px; + padding: 2px 4px !important; + font-size: 12px; +} + +.icons .item { + padding: 4px !important; font-size: 12px; } \ No newline at end of file diff --git a/frontend/app/components/ui/SlideModal/SlideModal.js b/frontend/app/components/ui/SlideModal/SlideModal.js index cc329463c..92f8ba710 100644 --- a/frontend/app/components/ui/SlideModal/SlideModal.js +++ b/frontend/app/components/ui/SlideModal/SlideModal.js @@ -1,12 +1,24 @@ import styles from './slideModal.css'; import cn from 'classnames'; export default class SlideModal extends React.PureComponent { - componentDidMount() { - document.addEventListener('keydown', this.keyPressHandler); - } + // componentDidMount() { + // document.addEventListener('keydown', this.keyPressHandler); + // } - componentWillUnmount() { - document.removeEventListener('keydown', this.keyPressHandler); + // componentWillUnmount() { + // document.removeEventListener('keydown', this.keyPressHandler); + // } + + componentDidUpdate(prevProps) { + if (prevProps.isDisplayed !== this.props.isDisplayed) { + if (this.props.isDisplayed) { + document.addEventListener('keydown', this.keyPressHandler); + document.body.classList.add('no-scroll'); + } else { + document.removeEventListener('keydown', this.keyPressHandler); + document.body.classList.remove('no-scroll'); + } + } } keyPressHandler = (e) => { diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 868379e86..6b9b5b70d 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -54,6 +54,36 @@ export const customOperators = [ { key: '>=', text: '>=', value: '>=' }, ] +export const metricTypes = [ + { text: 'Timeseries', value: 'timeseries' }, + { text: 'Table', value: 'table' }, +]; + +export const metricOf = [ + { text: 'Session Count', value: 'sessionCount', key: 'timeseries' }, + { text: 'Users', value: 'USERID', key: 'table' }, + { text: 'Issues', value: 'ISSUES', key: 'table' }, + { text: 'Browser', value: 'USERBROWSER', key: 'table' }, + { text: 'Device', value: 'USERDEVICE', key: 'table' }, + { text: 'Country', value: 'USERCOUNTRY', key: 'table' }, + { text: 'URL', value: 'VISITED_URL', key: 'table' }, +] + +export const issueOptions = [ + { text: 'Click Rage', value: 'click_rage' }, + { text: 'Dead Click', value: 'dead_click' }, + { text: 'Excessive Scrolling', value: 'excessive_scrolling' }, + { text: 'Bad Request', value: 'bad_request' }, + { text: 'Missing Resource', value: 'missing_resource' }, + { text: 'Memory', value: 'memory' }, + { text: 'CPU', value: 'cpu' }, + { text: 'Slow Resource', value: 'slow_resource' }, + { text: 'Slow Page Load', value: 'slow_page_load' }, + { text: 'Crash', value: 'crash' }, + { text: 'Custom', value: 'custom' }, + { text: 'JS Exception', value: 'js_exception' }, +] + export default { options, baseOperators, @@ -62,4 +92,7 @@ export default { booleanOperators, customOperators, getOperatorsByKeys, + metricTypes, + metricOf, + issueOptions, } \ No newline at end of file diff --git a/frontend/app/duck/filters.js b/frontend/app/duck/filters.js index 132996797..16c16aa5e 100644 --- a/frontend/app/duck/filters.js +++ b/frontend/app/duck/filters.js @@ -1,4 +1,4 @@ -import { fromJS, List, Map, Set } from 'immutable'; +import { List, Map, Set } from 'immutable'; import { errors as errorsRoute, isRoute } from "App/routes"; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; @@ -8,15 +8,6 @@ import withRequestState, { RequestTypes } from './requestStateCreator'; import { fetchList as fetchSessionList } from './sessions'; import { fetchList as fetchErrorsList } from './errors'; import { fetchListType, fetchType, saveType, editType, initType, removeType } from './funcTools/crud/types'; -import logger from 'App/logger'; - -import { newFiltersList } from 'Types/filter' -import NewFilter, { filtersMap } from 'Types/filter/newFilter'; - - -// for (var i = 0; i < newFiltersList.length; i++) { -// filterOptions[newFiltersList[i].category] = newFiltersList.filter(filter => filter.category === newFiltersList[i].category) -// } const ERRORS_ROUTE = errorsRoute(); @@ -44,11 +35,8 @@ const ADD_ATTRIBUTE = 'filters/ADD_ATTRIBUTE'; const EDIT_ATTRIBUTE = 'filters/EDIT_ATTRIBUTE'; const REMOVE_ATTRIBUTE = 'filters/REMOVE_ATTRIBUTE'; const SET_ACTIVE_FLOW = 'filters/SET_ACTIVE_FLOW'; - const UPDATE_VALUE = 'filters/UPDATE_VALUE'; -const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS'; - const initialState = Map({ instance: Filter(), activeFilter: null, diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index ad4ea944c..00799e5d7 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -161,7 +161,7 @@ export const applySavedSearch = (filter) => (dispatch, getState) => { export const fetchSessions = (filter) => (dispatch, getState) => { const _filter = filter ? filter : getState().getIn([ 'search', 'instance']); - return dispatch(applyFilter(_filter)); + // return dispatch(applyFilter(_filter)); // TODO uncomment this line }; export const updateSeries = (index, series) => ({ @@ -233,6 +233,10 @@ export const hasFilterApplied = (filters, filter) => { export const addFilter = (filter) => (dispatch, getState) => { filter.value = checkFilterValue(filter.value); + filter.subFilters = filter.subFilters ? filter.subFilters.map(subFilter => ({ + ...subFilter, + value: checkFilterValue(subFilter.value), + })) : null; const instance = getState().getIn([ 'search', 'instance']); if (hasFilterApplied(instance.filters, filter)) { diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 908add195..595cf3b3e 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -103,7 +103,7 @@ export default class AssistManager { if (document.hidden && getState().calling === CallingState.NoCall) { this.socket?.close() } - }, 15000) + }, 30000) } else { inactiveTimeout && clearTimeout(inactiveTimeout) this.socket?.open() diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css index 81e5ab814..3b7f5fe4b 100644 --- a/frontend/app/styles/main.css +++ b/frontend/app/styles/main.css @@ -141,4 +141,10 @@ margin: 25px 0; background-color: $gray-light; +} + +.no-scroll { + height: 100vh; + overflow-y: hidden; + padding-right: 15px; } \ No newline at end of file diff --git a/frontend/app/svg/icons/graph-up-arrow.svg b/frontend/app/svg/icons/graph-up-arrow.svg new file mode 100644 index 000000000..fd582e467 --- /dev/null +++ b/frontend/app/svg/icons/graph-up-arrow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/hash.svg b/frontend/app/svg/icons/hash.svg new file mode 100644 index 000000000..4621b1dac --- /dev/null +++ b/frontend/app/svg/icons/hash.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/table.svg b/frontend/app/svg/icons/table.svg new file mode 100644 index 000000000..5e70d22c4 --- /dev/null +++ b/frontend/app/svg/icons/table.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index 5e5e9aba8..deebf9cad 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -103,5 +103,11 @@ export default Record({ endTimestamp: this.end, }; }, + toTimestampstwo() { + return { + startTimestamp: this.start / 1000, + endTimestamp: this.end / 1000, + }; + }, } }); \ No newline at end of file diff --git a/frontend/app/types/customMetric.js b/frontend/app/types/customMetric.js index d5238a0aa..0686af87d 100644 --- a/frontend/app/types/customMetric.js +++ b/frontend/app/types/customMetric.js @@ -27,7 +27,11 @@ export const FilterSeries = Record({ export default Record({ metricId: undefined, name: 'Series', - viewType: 'lineChart', + metricType: 'table', + metricOf: 'USERID', + metricValue: ['sessionCount'], + metricFormat: 'sessionCount', + viewType: 'pieChart', series: List(), isPublic: true, startDate: '', diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 16f128975..655681796 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -15,6 +15,7 @@ export enum FilterType { NUMBER = "NUMBER", DURATION = "DURATION", MULTIPLE = "MULTIPLE", + SUB_FILTERS = "SUB_FILTERS", COUNTRY = "COUNTRY", DROPDOWN = "DROPDOWN", MULTIPLE_DROPDOWN = "MULTIPLE_DROPDOWN", @@ -61,4 +62,8 @@ export enum FilterKey { AVG_CPU_LOAD = "AVG_CPU_LOAD", AVG_MEMORY_USAGE = "AVG_MEMORY_USAGE", FETCH_FAILED = "FETCH_FAILED", + FETCH = "FETCH", + FETCH_URL = "FETCH_URL", + FETCH_STATUS = "FETCH_STATUS", + FETCH_METHOD = "FETCH_METHOD", } \ No newline at end of file diff --git a/frontend/app/types/filter/index.js b/frontend/app/types/filter/index.js index 957e1dfb8..386ea96a0 100644 --- a/frontend/app/types/filter/index.js +++ b/frontend/app/types/filter/index.js @@ -236,22 +236,4 @@ export const operatorOptions = (filter) => { case KEYS.CLICK_RAGE: return [{ key: 'onAnything', text: 'on anything', value: 'true' }] } -} - -const NewFilterType = (key, category, label, icon, isEvent = false) => { - return { - key: key, - category: category, - label: label, - icon: icon, - isEvent: isEvent, - operators: operatorOptions({ key }), - value: [""] - } -} - -export const newFiltersList = [ - NewFilterType(TYPES.CLICK, 'Gear', 'Click', 'filters/click', true), - NewFilterType(TYPES.CLICK, 'Gear', 'Input', 'filters/click', true), - NewFilterType(TYPES.CONSOLE, 'Other', 'Console', 'filters/click', true), -]; \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index d4cb905a1..154db3f23 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -6,21 +6,6 @@ import { capitalize } from 'App/utils'; const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i })); const containsFilters = [{ key: 'contains', text: 'contains', value: 'contains' }] -const ISSUE_OPTIONS = [ - { text: 'Click Rage', value: 'click_rage' }, - { text: 'Dead Click', value: 'dead_click' }, - { text: 'Excessive Scrolling', value: 'excessive_scrolling' }, - { text: 'Bad Request', value: 'bad_request' }, - { text: 'Missing Resource', value: 'missing_resource' }, - { text: 'Memory', value: 'memory' }, - { text: 'CPU', value: 'cpu' }, - { text: 'Slow Resource', value: 'slow_resource' }, - { text: 'Slow Page Load', value: 'slow_page_load' }, - { text: 'Crash', value: 'crash' }, - { text: 'Custom', value: 'custom' }, - { text: 'JS Exception', value: 'js_exception' }, -] - export const filtersMap = { // EVENTS [FilterKey.CLICK]: { key: FilterKey.CLICK, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Click', operator: 'on', operatorOptions: filterOptions.targetOperators, icon: 'filters/click', isEvent: true }, @@ -48,13 +33,18 @@ export const filtersMap = { [FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, // PERFORMANCE + [FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.PERFORMANCE, label: 'Fetch Request', subFilters: [ + { key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, + { key: FilterKey.FETCH_STATUS, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, + { key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, + ], icon: 'filters/fetch-failed', isEvent: true }, [FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.TTFB]: { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, [FilterKey.FETCH_FAILED]: { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch-failed', isEvent: true }, - [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: ISSUE_OPTIONS }, + [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions }, } export const liveFiltersMap = { @@ -121,17 +111,19 @@ export default Record({ isEvent: false, index: 0, options: [], + + subFilters: [], }, { keyKey: "_key", fromJS: ({ value, key, type, ...filter }) => { - // const _filter = filtersMap[key] || filtersMap[type] || {}; const _filter = filtersMap[type]; return { ...filter, ..._filter, key: _filter.key, type: _filter.type, // camelCased(filter.type.toLowerCase()), - value: value.length === 0 ? [""] : value, // make sure there an empty value + value: value.length === 0 ? [""] : value, + // subFilters: filter.subFilters.map(this), } }, }) @@ -142,33 +134,29 @@ export default Record({ * @returns */ export const generateFilterOptions = (map) => { - const _options = {}; + const filterSection = {}; Object.keys(map).forEach(key => { const filter = map[key]; - if (_options.hasOwnProperty(filter.category)) { - _options[filter.category].push(filter); + if (filterSection.hasOwnProperty(filter.category)) { + filterSection[filter.category].push(filter); } else { - _options[filter.category] = [filter]; + filterSection[filter.category] = [filter]; } }); - return _options; + return filterSection; } export const generateLiveFilterOptions = (map) => { - const _options = {}; + const filterSection = {}; Object.keys(map).filter(i => map[i].isLive).forEach(key => { const filter = map[key]; filter.operator = 'contains'; - // filter.type = FilterType.STRING; - // filter.type = FilterType.AUTOCOMPLETE_LOCAL; - // filter.options = countryOptions; - // filter.operatorOptions = [{ key: 'contains', text: 'contains', value: 'contains' }] - if (_options.hasOwnProperty(filter.category)) { - _options[filter.category].push(filter); + if (filterSection.hasOwnProperty(filter.category)) { + filterSection[filter.category].push(filter); } else { - _options[filter.category] = [filter]; + filterSection[filter.category] = [filter]; } }); - return _options; + return filterSection; } \ No newline at end of file diff --git a/frontend/app/utils.js b/frontend/app/utils.js index 52b2a9d6a..4c1a0c607 100644 --- a/frontend/app/utils.js +++ b/frontend/app/utils.js @@ -226,4 +226,10 @@ export const iceServerConfigFromString = (str) => { return server } }) +} + +export const isGreaterOrEqualVersion = (version, compareTo) => { + const [major, minor, patch] = version.split("-")[0].split('.'); + const [majorC, minorC, patchC] = compareTo.split("-")[0].split('.'); + return (major > majorC) || (major === majorC && minor > minorC) || (major === majorC && minor === minorC && patch >= patchC); } \ No newline at end of file diff --git a/frontend/env.js b/frontend/env.js index 1b1beb4bc..7d3ab7e6e 100644 --- a/frontend/env.js +++ b/frontend/env.js @@ -13,7 +13,7 @@ const oss = { ORIGIN: () => 'window.location.origin', API_EDP: () => 'window.location.origin + "/api"', ASSETS_HOST: () => 'window.location.origin + "/assets"', - VERSION: '1.5.1', + VERSION: '1.5.2', SOURCEMAP: true, MINIO_ENDPOINT: process.env.MINIO_ENDPOINT, MINIO_PORT: process.env.MINIO_PORT, diff --git a/scripts/helmcharts/init.sh b/scripts/helmcharts/init.sh index 428ed82eb..2df3b8450 100644 --- a/scripts/helmcharts/init.sh +++ b/scripts/helmcharts/init.sh @@ -15,7 +15,7 @@ fatal() exit 1 } -version="v1.5.1" +version="v1.5.2" usr=`whoami` # Installing k3s diff --git a/scripts/helmcharts/vars.yaml b/scripts/helmcharts/vars.yaml index e7f59ab13..ed0ab4c05 100644 --- a/scripts/helmcharts/vars.yaml +++ b/scripts/helmcharts/vars.yaml @@ -1,4 +1,4 @@ -fromVersion: "v1.5.1" +fromVersion: "v1.5.2" # Databases specific variables postgresql: &postgres # For generating passwords diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index 9334dd800..d79a507cc 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -162,7 +162,7 @@ module.exports = { wsRouter, start: (server) => { io = _io(server, { - maxHttpBufferSize: 1e6, + maxHttpBufferSize: 5e6, cors: { origin: "*", methods: ["GET", "POST", "PUT"]