Skip to content

Commit

Permalink
feat: Batch Exports UI rework (#16926)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Aug 16, 2023
1 parent 8394a77 commit e1dc70e
Show file tree
Hide file tree
Showing 55 changed files with 2,122 additions and 2,475 deletions.
23 changes: 22 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,33 @@
"console": "integratedTerminal",
"python": "${workspaceFolder}/env/bin/python",
"cwd": "${workspaceFolder}"
},
{
"name": "Temporal Worker",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["start_temporal_worker"],
"django": true,
"env": {
"PYTHONUNBUFFERED": "1",
"DJANGO_SETTINGS_MODULE": "posthog.settings",
"DEBUG": "1",
"CLICKHOUSE_SECURE": "False",
"KAFKA_HOSTS": "localhost",
"DATABASE_URL": "postgres://posthog:posthog@localhost:5432/posthog",
"SKIP_SERVICE_VERSION_REQUIREMENTS": "1",
"PRINT_SQL": "1"
},
"console": "integratedTerminal",
"python": "${workspaceFolder}/env/bin/python",
"cwd": "${workspaceFolder}"
}
],
"compounds": [
{
"name": "PostHog",
"configurations": ["Backend", "Celery", "Frontend", "Plugin Server"],
"configurations": ["Backend", "Celery", "Frontend", "Plugin Server", "Temporal Worker"],
"stopAll": true
}
]
Expand Down
14 changes: 14 additions & 0 deletions ee/api/test/__snapshots__/test_organization_resource_access.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@
LIMIT 100 /*controller='organization_resource_access-list',route='api/organizations/%28%3FP%3Cparent_lookup_organization_id%3E%5B%5E/.%5D%2B%29/resource_access/%3F%24'*/
'
---
# name: TestOrganizationResourceAccessAPI.test_list_organization_resource_access_is_not_nplus1.15
'
SELECT "ee_organizationresourceaccess"."id",
"ee_organizationresourceaccess"."resource",
"ee_organizationresourceaccess"."access_level",
"ee_organizationresourceaccess"."organization_id",
"ee_organizationresourceaccess"."created_by_id",
"ee_organizationresourceaccess"."created_at",
"ee_organizationresourceaccess"."updated_at"
FROM "ee_organizationresourceaccess"
WHERE "ee_organizationresourceaccess"."organization_id" = '00000000-0000-0000-0000-000000000000'::uuid
LIMIT 100 /*controller='organization_resource_access-list',route='api/organizations/%28%3FP%3Cparent_lookup_organization_id%3E%5B%5E/.%5D%2B%29/resource_access/%3F%24'*/
'
---
# name: TestOrganizationResourceAccessAPI.test_list_organization_resource_access_is_not_nplus1.2
'
SELECT "posthog_organization"."id",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-exports--create-export.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/layout/navigation/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ function Pages(): JSX.Element {
to={urls.projectApps()}
/>
)}

{Object.keys(frontendApps).length > 0 && <SideBarApps />}
</>
) : null}
Expand Down
69 changes: 67 additions & 2 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import {
Survey,
TeamType,
UserType,
BatchExportConfiguration,
BatchExportRun,
NotebookNodeType,
} from '~/types'
import { getCurrentOrganizationId, getCurrentTeamId } from './utils/logics'
Expand Down Expand Up @@ -513,8 +515,28 @@ class ApiRequest {
return this.notebooks(teamId).addPathComponent(id)
}

// Request finalization
// Batch Exports
public batchExports(teamId?: TeamType['id']): ApiRequest {
return this.projectsDetail(teamId).addPathComponent('batch_exports')
}

public batchExport(id: BatchExportConfiguration['id'], teamId?: TeamType['id']): ApiRequest {
return this.batchExports(teamId).addPathComponent(id)
}

public batchExportRuns(id: BatchExportConfiguration['id'], teamId?: TeamType['id']): ApiRequest {
return this.batchExports(teamId).addPathComponent(id).addPathComponent('runs')
}

public batchExportRun(
id: BatchExportConfiguration['id'],
runId: BatchExportRun['id'],
teamId?: TeamType['id']
): ApiRequest {
return this.batchExportRuns(id, teamId).addPathComponent(runId)
}

// Request finalization
public async get(options?: ApiMethodOptions): Promise<any> {
return await api.get(this.assembleFullUrl(), options)
}
Expand Down Expand Up @@ -1282,6 +1304,49 @@ const api = {
},
},

batchExports: {
async list(params: Record<string, any> = {}): Promise<CountedPaginatedResponse<BatchExportConfiguration>> {
return await new ApiRequest().batchExports().withQueryString(toParams(params)).get()
},
async get(id: BatchExportConfiguration['id']): Promise<BatchExportConfiguration> {
return await new ApiRequest().batchExport(id).get()
},
async update(
id: BatchExportConfiguration['id'],
data: Partial<BatchExportConfiguration>
): Promise<BatchExportConfiguration> {
return await new ApiRequest().batchExport(id).update({ data })
},

async create(data?: Partial<BatchExportConfiguration>): Promise<BatchExportConfiguration> {
return await new ApiRequest().batchExports().create({ data })
},
async delete(id: BatchExportConfiguration['id']): Promise<BatchExportConfiguration> {
return await new ApiRequest().batchExport(id).delete()
},

async pause(id: BatchExportConfiguration['id']): Promise<BatchExportConfiguration> {
return await new ApiRequest().batchExport(id).withAction('pause').create()
},

async unpause(id: BatchExportConfiguration['id']): Promise<BatchExportConfiguration> {
return await new ApiRequest().batchExport(id).withAction('unpause').create()
},

async listRuns(
id: BatchExportConfiguration['id'],
params: Record<string, any> = {}
): Promise<PaginatedResponse<BatchExportRun>> {
return await new ApiRequest().batchExportRuns(id).withQueryString(toParams(params)).get()
},
async createBackfill(
id: BatchExportConfiguration['id'],
data: Pick<BatchExportConfiguration, 'start_at' | 'end_at'>
): Promise<BatchExportRun> {
return await new ApiRequest().batchExport(id).withAction('backfill').create({ data })
},
},

earlyAccessFeatures: {
async get(featureId: EarlyAccessFeatureType['id']): Promise<EarlyAccessFeatureType> {
return await new ApiRequest().earlyAccessFeature(featureId).get()
Expand Down Expand Up @@ -1452,7 +1517,7 @@ const api = {
},

/** Fetch data from specified URL. The result already is JSON-parsed. */
async get(url: string, options?: ApiMethodOptions): Promise<any> {
async get<T = any>(url: string, options?: ApiMethodOptions): Promise<T> {
const res = await api.getResponse(url, options)
return await getJSONOrThrow(res)
},
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/lib/components/UUIDShortener.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { copyToClipboard } from 'lib/utils'

export function truncateUuid(uuid: string): string {
// Simple function to truncate a UUID. Useful for more simple displaying but should always be made clear it is truncated.
return uuid
.split('-')
.map((x) => x.slice(0, 2))
.join('')
}

export function UUIDShortener({ uuid, clickToCopy = false }: { uuid: string; clickToCopy?: boolean }): JSX.Element {
return (
<Tooltip
title={
<>
<span className="whitespace-nowrap">{uuid}</span>
{clickToCopy && (
<>
<br />
Double click to copy
</>
)}
</>
}
>
<span onDoubleClick={clickToCopy ? () => copyToClipboard(uuid) : undefined} title={uuid}>
{truncateUuid(uuid)}...
</span>
</Tooltip>
)
}
58 changes: 57 additions & 1 deletion frontend/src/lib/lemon-ui/LemonCalendar/LemonCalendarSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { LemonCalendar } from 'lib/lemon-ui/LemonCalendar/LemonCalendar'
import { useState } from 'react'
import { dayjs } from 'lib/dayjs'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonButton, LemonButtonProps, LemonButtonWithSideAction, SideAction } from 'lib/lemon-ui/LemonButton'
import { IconClose } from 'lib/lemon-ui/icons'
import { Popover } from '../Popover'

export interface LemonCalendarSelectProps {
value?: dayjs.Dayjs | null
Expand Down Expand Up @@ -58,3 +59,58 @@ export function LemonCalendarSelect({ value, onChange, months, onClose }: LemonC
</div>
)
}

export function LemonCalendarSelectInput(
props: LemonCalendarSelectProps & {
onChange: (date: dayjs.Dayjs | null) => void
buttonProps?: LemonButtonProps
placeholder?: string
clearable?: boolean
}
): JSX.Element {
const { buttonProps, placeholder, clearable, ...calendarProps } = props
const [visible, setVisible] = useState(false)

const showClear = props.value && clearable

const ButtonComponent = showClear ? LemonButtonWithSideAction : LemonButton

return (
<Popover
actionable
onClickOutside={() => setVisible(false)}
visible={visible}
overlay={
<LemonCalendarSelect
{...calendarProps}
onChange={(value) => {
props.onChange(value)
setVisible(false)
}}
onClose={() => {
setVisible(false)
props.onClose?.()
}}
/>
}
>
<ButtonComponent
onClick={() => setVisible(true)}
type="secondary"
status="stealth"
fullWidth
sideAction={
showClear
? {
icon: <IconClose />,
onClick: () => props.onChange(null),
}
: (undefined as unknown as SideAction) // We know it will be a normal button if not clearable
}
{...props.buttonProps}
>
{props.value?.format('MMMM D, YYYY') ?? placeholder ?? 'Select date'}
</ButtonComponent>
</Popover>
)
}
6 changes: 3 additions & 3 deletions frontend/src/scenes/appScenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export const appScenes: Record<Scene, () => any> = {
[Scene.Cohort]: () => import('./cohorts/Cohort'),
[Scene.DataManagement]: () => import('./data-management/events/EventDefinitionsTable'),
[Scene.Events]: () => import('./events/Events'),
[Scene.Exports]: () => import('./exports/ExportsList'),
[Scene.CreateExport]: () => import('./exports/CreateExport'),
[Scene.ViewExport]: () => import('./exports/ViewExport'),
[Scene.BatchExports]: () => import('./batch_exports/BatchExportsListScene'),
[Scene.BatchExportEdit]: () => import('./batch_exports/BatchExportEditScene'),
[Scene.BatchExport]: () => import('./batch_exports/BatchExportScene'),
[Scene.Actions]: () => import('./actions/ActionsTable'),
[Scene.EventDefinitions]: () => import('./data-management/events/EventDefinitionsTable'),
[Scene.EventDefinition]: () => import('./data-management/definition/DefinitionView'),
Expand Down
20 changes: 11 additions & 9 deletions frontend/src/scenes/apps/appMetricsSceneLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { loaders } from 'kea-loaders'
import type { appMetricsSceneLogicType } from './appMetricsSceneLogicType'
import { urls } from 'scenes/urls'
import { Breadcrumb, PluginConfigWithPluginInfo, UserBasicType } from '~/types'
import api from 'lib/api'
import api, { PaginatedResponse } from 'lib/api'
import { teamLogic } from 'scenes/teamLogic'
import { actionToUrl, urlToAction } from 'kea-router'
import { toParams } from 'lib/utils'
Expand Down Expand Up @@ -142,7 +142,7 @@ export const appMetricsSceneLogic = kea<appMetricsSceneLogicType>([
null as PluginConfigWithPluginInfo | null,
{
loadPluginConfig: async () => {
return await api.get(
return await api.get<PluginConfigWithPluginInfo>(
`api/projects/${teamLogic.values.currentTeamId}/plugin_configs/${props.pluginConfigId}`
)
},
Expand All @@ -152,23 +152,25 @@ export const appMetricsSceneLogic = kea<appMetricsSceneLogicType>([
null as AppMetricsResponse | null,
{
loadMetrics: async () => {
if (values.activeTab && values.dateFrom) {
const params = toParams({ category: values.activeTab, date_from: values.dateFrom })
return await api.get(
`api/projects/${teamLogic.values.currentTeamId}/app_metrics/${props.pluginConfigId}?${params}`
)
if (!values.activeTab || !values.dateFrom) {
return null
}

const params = toParams({ category: values.activeTab, date_from: values.dateFrom })
return await api.get(
`api/projects/${teamLogic.values.currentTeamId}/app_metrics/${props.pluginConfigId}?${params}`
)
},
},
],
historicalExports: [
[] as Array<HistoricalExportInfo>,
{
loadHistoricalExports: async () => {
const { results } = await api.get(
const { results } = await api.get<PaginatedResponse<HistoricalExportInfo>>(
`api/projects/${teamLogic.values.currentTeamId}/app_metrics/${props.pluginConfigId}/historical_exports`
)
return results as Array<HistoricalExportInfo>
return results
},
},
],
Expand Down
Loading

0 comments on commit e1dc70e

Please sign in to comment.