Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions backend/apps/cloud/src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
} from '../common/utils'
import { GetCustomEventsDto } from './dto/get-custom-events.dto'
import { GetFiltersDto } from './dto/get-filters.dto'
import { GetVersionFiltersDto } from './dto/get-version-filters.dto'
import {
IFunnel,
IGetFunnel,
Expand Down Expand Up @@ -525,6 +526,26 @@ export class AnalyticsController {
return this.analyticsService.getErrorsFilters(pid, type)
}

@Get('filters/versions')
@Auth(true, true)
async getVersionFilters(
@Query() data: GetVersionFiltersDto,
@CurrentUserId() uid: string,
@Headers() headers: { 'x-password'?: string },
) {
const { pid, type, column } = data

await this.analyticsService.checkProjectAccess(
pid,
uid,
headers['x-password'],
)

await this.analyticsService.checkBillingAccess(pid)

return this.analyticsService.getVersionFilters(pid, type, column)
}

@Get('chart')
@Auth(true, true)
async getChartData(
Expand Down
28 changes: 28 additions & 0 deletions backend/apps/cloud/src/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2004,6 +2004,34 @@ export class AnalyticsService {
return _map(data, type)
}

async getVersionFilters(
pid: string,
type: 'traffic' | 'errors',
column: 'br' | 'os',
): Promise<Array<{ name: string; version: string }>> {
const safeTable = type === 'errors' ? 'errors' : 'analytics'
const safeVersionCol = column === 'br' ? 'brv' : 'osv'

const query = `
SELECT ${column}, ${safeVersionCol}
FROM ${safeTable}
WHERE pid={pid:FixedString(12)} AND ${column} IS NOT NULL AND ${safeVersionCol} IS NOT NULL
GROUP BY ${column}, ${safeVersionCol}
`

const { data } = await clickhouse
.query({
query,
query_params: { pid },
})
.then(resultSet => resultSet.json<Record<string, string>[]>())

return data.map(row => ({
name: row[column],
version: row[safeVersionCol],
}))
}

async generateParams(
parsedFilters: Array<{ [key: string]: string }>,
subQuery: string,
Expand Down
38 changes: 38 additions & 0 deletions backend/apps/cloud/src/analytics/dto/get-version-filters.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsNotEmpty, Matches, IsIn } from 'class-validator'
import { PID_REGEX } from '../../common/constants'

export class GetVersionFiltersDto {
@ApiProperty({
example: 'aUn1quEid-3',
required: true,
description: 'The project ID',
})
@IsNotEmpty()
@Matches(PID_REGEX, { message: 'The provided Project ID (pid) is incorrect' })
pid: string

@ApiProperty({
example: 'traffic',
required: true,
description: 'Data type: traffic or errors',
enum: ['traffic', 'errors'],
})
@IsNotEmpty()
@IsIn(['traffic', 'errors'], {
message: 'type must be either traffic or errors',
})
type: 'traffic' | 'errors'

@ApiProperty({
example: 'br',
required: true,
description: 'Column type: br (browser) or os',
enum: ['br', 'os'],
})
@IsNotEmpty()
@IsIn(['br', 'os'], {
message: 'column must be either br or os',
})
column: 'br' | 'os'
}
19 changes: 19 additions & 0 deletions backend/apps/community/src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { PageviewsDto } from './dto/pageviews.dto'
import { EventsDto } from './dto/events.dto'
import { GetDataDto, ChartRenderMode } from './dto/getData.dto'
import { GetFiltersDto } from './dto/get-filters.dto'
import { GetVersionFiltersDto } from './dto/get-version-filters.dto'
import { GetCustomEventMetadata } from './dto/get-custom-event-meta.dto'
import { GetPagePropertyMetaDto } from './dto/get-page-property-meta.dto'
import { GetUserFlowDto } from './dto/getUserFlow.dto'
Expand Down Expand Up @@ -1400,6 +1401,24 @@ export class AnalyticsController {
return this.analyticsService.getErrorsFilters(pid, type)
}

@Get('filters/versions')
@Auth(true, true)
async getVersionFilters(
@Query() data: GetVersionFiltersDto,
@CurrentUserId() uid: string,
@Headers() headers: { 'x-password'?: string },
) {
const { pid, type, column } = data

await this.analyticsService.checkProjectAccess(
pid,
uid,
headers['x-password'],
)

return this.analyticsService.getVersionFilters(pid, type, column)
}

@Post('error')
@Public()
async logError(@Body() errorDTO: ErrorDto, @Headers() headers, @Ip() reqIP) {
Expand Down
28 changes: 28 additions & 0 deletions backend/apps/community/src/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,34 @@ export class AnalyticsService {
return _map(data, type)
}

async getVersionFilters(
pid: string,
type: 'traffic' | 'errors',
column: 'br' | 'os',
): Promise<Array<{ name: string; version: string }>> {
const safeTable = type === 'errors' ? 'errors' : 'analytics'
const safeVersionCol = column === 'br' ? 'brv' : 'osv'

const query = `
SELECT ${column}, ${safeVersionCol}
FROM ${safeTable}
WHERE pid={pid:FixedString(12)} AND ${column} IS NOT NULL AND ${safeVersionCol} IS NOT NULL
GROUP BY ${column}, ${safeVersionCol}
`

const { data } = await clickhouse
.query({
query,
query_params: { pid },
})
.then(resultSet => resultSet.json<Record<string, string>[]>())

return data.map(row => ({
name: row[column],
version: row[safeVersionCol],
}))
}

getDataTypeColumns(dataType: DataType): string[] {
if (dataType === DataType.ANALYTICS) {
return TRAFFIC_COLUMNS
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsNotEmpty, Matches, IsIn } from 'class-validator'
import { PID_REGEX } from '../../common/constants'

export class GetVersionFiltersDto {
@ApiProperty({
example: 'aUn1quEid-3',
required: true,
description: 'The project ID',
})
@IsNotEmpty()
@Matches(PID_REGEX, { message: 'The provided Project ID (pid) is incorrect' })
pid: string

@ApiProperty({
example: 'traffic',
required: true,
description: 'Data type: traffic or errors',
enum: ['traffic', 'errors'],
})
@IsNotEmpty()
@IsIn(['traffic', 'errors'], {
message: 'type must be either traffic or errors',
})
type: 'traffic' | 'errors'

@ApiProperty({
example: 'br',
required: true,
description: 'Column type: br (browser) or os',
enum: ['br', 'os'],
})
@IsNotEmpty()
@IsIn(['br', 'os'], {
message: 'column must be either br or os',
})
column: 'br' | 'os'
}
12 changes: 12 additions & 0 deletions web/app/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,18 @@ export const getErrorsFilters = (pid: string, type: string, password = '') =>
throw error
})

export const getVersionFilters = (pid: string, type: 'traffic' | 'errors', column: 'br' | 'os', password = '') =>
api
.get(`log/filters/versions?pid=${pid}&type=${type}&column=${column}`, {
headers: {
'x-password': password,
},
})
.then((response): Array<{ name: string; version: string }> => response.data)
.catch((error) => {
throw error
})

export const resetFilters = (pid: string, type: string, filters: string[]) =>
api
.delete(`project/reset-filters/${pid}?type=${type}&filters=${JSON.stringify(filters)}`)
Expand Down
24 changes: 22 additions & 2 deletions web/app/lib/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,28 @@ export const PERIOD_PAIRS_COMPARE = {
export const TRAFFIC_PANELS_ORDER = ['location', 'pg', 'devices', 'traffic-sources']
export const PERFORMANCE_PANELS_ORDER = ['location', 'pg', 'devices']
export const ERROR_PANELS_ORDER = ['location', 'pg', 'devices']
export const FILTERS_PANELS_ORDER = ['cc', 'pg', 'entryPage', 'exitPage', 'br', 'os', 'ref', 'lc', 'dv']
export const ERRORS_FILTERS_PANELS_ORDER = ['cc', 'pg', 'br', 'os', 'lc', 'dv']
export const FILTERS_PANELS_ORDER = [
'pg',
'entryPage',
'exitPage',
'host',
'cc',
'rg',
'ct',
'lc',
'dv',
'br',
'brv',
'os',
'osv',
'ref',
'so',
'me',
'ca',
'te',
'co',
]
export const ERRORS_FILTERS_PANELS_ORDER = ['cc', 'rg', 'ct', 'pg', 'br', 'brv', 'os', 'osv', 'lc', 'dv']

// the maximum amount of months user can go back when picking a date in flat picker (project view)
export const MAX_MONTHS_IN_PAST = 24
Expand Down
2 changes: 1 addition & 1 deletion web/app/pages/Project/View/components/AddAViewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ const AddAViewModal = ({ onSubmit, showModal, setShowModal, tnMapping, defaultVi
<div className='mt-2'>
{_map(activeFilters, ({ filter, column, isExclusive, isContains }) => (
<Filter
key={`${column}-${filter}`}
key={`${column}-${filter}-${isExclusive}-${isContains}`}
onRemoveFilter={(e) => {
e.preventDefault()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const RefreshStatsButton = ({ onRefresh }: RefreshStatsButtonProps) => {
title={t('project.refreshStats')}
onClick={handleManualRefresh}
className={cn(
'group relative rounded-md border border-transparent bg-gray-50 p-2 hover:border-gray-300 hover:bg-white focus:z-10 focus:ring-1 focus:ring-indigo-500 focus:outline-hidden dark:bg-slate-900 hover:dark:border-slate-700/80 dark:hover:bg-slate-800 focus:dark:ring-gray-200',
'group relative rounded-md border border-transparent p-2 hover:border-gray-300 hover:bg-white focus:z-10 focus:ring-1 focus:ring-indigo-500 focus:outline-hidden hover:dark:border-slate-700/80 dark:hover:bg-slate-800 focus:dark:ring-gray-200',
{
'cursor-not-allowed opacity-50': authLoading,
},
Expand Down
Loading