diff --git a/apps/studio/components/interfaces/Account/AuditLogs.tsx b/apps/studio/components/interfaces/Account/AuditLogs.tsx index f5f49c5fc0c..63a96069040 100644 --- a/apps/studio/components/interfaces/Account/AuditLogs.tsx +++ b/apps/studio/components/interfaces/Account/AuditLogs.tsx @@ -15,6 +15,7 @@ import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query' import { useProjectsQuery } from 'data/projects/projects-query' import { Alert, Button } from 'ui' import { TimestampInfo } from 'ui-patterns' +import { formatSelectedDateRange } from '../Organization/AuditLogs/AuditLogs.utils' const AuditLogs = () => { const currentTime = dayjs().utc().set('millisecond', 0) @@ -70,6 +71,9 @@ const AuditLogs = () => { } }) + const minDate = dayjs().subtract(retentionPeriod, 'days') + const maxDate = dayjs() + return ( <>
@@ -91,47 +95,12 @@ const AuditLogs = () => { triggerButtonTitle="" from={dateRange.from} to={dateRange.to} - minDate={dayjs().subtract(retentionPeriod, 'days').toDate()} - maxDate={dayjs().toDate()} + minDate={minDate.toDate()} + maxDate={maxDate.toDate()} onChange={(value) => { if (value.from !== null && value.to !== null) { - const current = dayjs() - const from = dayjs(value.from) - .hour(current.hour()) - .minute(current.minute()) - .second(current.second()) - const to = dayjs(value.to) - .hour(current.hour()) - .minute(current.minute()) - .second(current.second()) - - if (from.date() === to.date()) { - // [Joshen] If a single date is selected, we either set the "from" to start from 00:00 - // or "to" to end at 23:59 depending on which date was selected - if (from.date() === current.date()) { - setDateRange({ - from: from - .set('hour', 0) - .set('minute', 0) - .set('second', 0) - .utc() - .toISOString(), - to: to.utc().toISOString(), - }) - } else { - setDateRange({ - from: from.utc().toISOString(), - to: to - .set('hour', 23) - .set('minute', 59) - .set('second', 59) - .utc() - .toISOString(), - }) - } - } else { - setDateRange({ from: from.utc().toISOString(), to: to.utc().toISOString() }) - } + const { from, to } = formatSelectedDateRange(value) + setDateRange({ from, to }) } }} renderFooter={() => { diff --git a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx index 5abde526b91..0895737379e 100644 --- a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx +++ b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx @@ -32,6 +32,7 @@ import { WarningIcon, } from 'ui' import { Admonition } from 'ui-patterns' +import { formatSelectedDateRange } from './AuditLogs.utils' // [Joshen considerations] // - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible @@ -79,23 +80,6 @@ const AuditLogs = () => { } ) - // This feature depends on the subscription tier of the user. Free user can view logs up to 1 day - // in the past. The API limits the logs to maximum of 1 day and 5 minutes so when the page is - // viewed for more than 5 minutes, the call parameters needs to be updated. This also works with - // higher tiers (7 days of logs).The user will see a loading shimmer. - useEffect(() => { - const duration = dayjs(dateRange.from).diff(dayjs(dateRange.to)) - const interval = setInterval(() => { - const currentTime = dayjs().utc().set('millisecond', 0) - setDateRange({ - from: currentTime.add(duration).toISOString(), - to: currentTime.toISOString(), - }) - }, 5 * 60000) - - return () => clearInterval(interval) - }, [dateRange.from, dateRange.to]) - const activeMembers = (members ?? []).filter((x) => !x.invited_at) const roles = [...(rolesData?.org_scoped_roles ?? []), ...(rolesData?.project_scoped_roles ?? [])] @@ -123,6 +107,25 @@ const AuditLogs = () => { }) const currentOrganization = organizations?.find((o) => o.slug === slug) + const minDate = dayjs().subtract(retentionPeriod, 'days') + const maxDate = dayjs() + + // This feature depends on the subscription tier of the user. Free user can view logs up to 1 day + // in the past. The API limits the logs to maximum of 1 day and 5 minutes so when the page is + // viewed for more than 5 minutes, the call parameters needs to be updated. This also works with + // higher tiers (7 days of logs).The user will see a loading shimmer. + useEffect(() => { + const duration = dayjs(dateRange.from).diff(dayjs(dateRange.to)) + const interval = setInterval(() => { + const currentTime = dayjs().utc().set('millisecond', 0) + setDateRange({ + from: currentTime.add(duration).toISOString(), + to: currentTime.toISOString(), + }) + }, 5 * 60000) + + return () => clearInterval(interval) + }, [dateRange.from, dateRange.to]) if (!canReadAuditLogs) { return ( @@ -208,23 +211,11 @@ const AuditLogs = () => { triggerButtonTitle="" from={dateRange.from} to={dateRange.to} - minDate={dayjs().subtract(retentionPeriod, 'days').toDate()} - maxDate={dayjs().toDate()} + minDate={minDate.toDate()} + maxDate={maxDate.toDate()} onChange={(value) => { if (value.from !== null && value.to !== null) { - const current = dayjs().utc() - const from = dayjs(value.from) - .utc() - .hour(current.hour()) - .minute(current.minute()) - .second(current.second()) - .toISOString() - const to = dayjs(value.to) - .utc() - .hour(current.hour()) - .minute(current.minute()) - .second(current.second()) - .toISOString() + const { from, to } = formatSelectedDateRange(value) setDateRange({ from, to }) } }} diff --git a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.utils.ts b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.utils.ts new file mode 100644 index 00000000000..c5429c8e6c3 --- /dev/null +++ b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.utils.ts @@ -0,0 +1,34 @@ +import { DatePickerToFrom } from 'components/interfaces/Settings/Logs/Logs.types' +import dayjs from 'dayjs' + +// [Joshen] Mainly to handle if a single date is selected - currently just for Audit Logs as +// i'm on the fence if this logic should be within the DatePicker component itself +// e.g for Logs.DatePicker which uses this component, the component itself has its own time selection UI +// JFYI currentDate is just a parameter so that I can run tests for this + +export const formatSelectedDateRange = (value: DatePickerToFrom) => { + const current = dayjs() + const from = dayjs(value.from) + .hour(current.hour()) + .minute(current.minute()) + .second(current.second()) + const to = dayjs(value.to).hour(current.hour()).minute(current.minute()).second(current.second()) + + if (from.date() === to.date()) { + // [Joshen] If a single date is selected, we either set the "from" to start from 00:00 + // or "to" to end at 23:59 depending on which date was selected + if (from.date() === current.date()) { + return { + from: from.set('hour', 0).set('minute', 0).set('second', 0).utc().toISOString(), + to: to.utc().toISOString(), + } + } else { + return { + from: from.utc().toISOString(), + to: to.set('hour', 23).set('minute', 59).set('second', 59).utc().toISOString(), + } + } + } else { + return { from: from.utc().toISOString(), to: to.utc().toISOString() } + } +} diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx b/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx index f48b236428b..2910f150828 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx @@ -1,7 +1,8 @@ -import { DatePicker } from 'components/ui/DatePicker' import dayjs from 'dayjs' -import { useEffect, useState } from 'react' +import { Clock } from 'lucide-react' +import { ComponentProps, PropsWithChildren, useEffect, useState } from 'react' +import { DatePicker } from 'components/ui/DatePicker' import { Alert, Button, @@ -13,15 +14,14 @@ import { } from 'ui' import { LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD, getDefaultHelper } from './Logs.constants' import type { DatetimeHelper } from './Logs.types' -import { Clock } from 'lucide-react' interface Props { to: string from: string - onChange: React.ComponentProps['onChange'] + onChange: ComponentProps['onChange'] helpers: DatetimeHelper[] } -const DatePickers: React.FC = ({ to, from, onChange, helpers }) => { +const DatePickers = ({ to, from, onChange, helpers }: PropsWithChildren) => { const defaultHelper = getDefaultHelper(helpers) const [helperValue, setHelperValue] = useState(to || from ? '' : defaultHelper.text) diff --git a/apps/studio/data/organizations/organization-audit-logs-query.ts b/apps/studio/data/organizations/organization-audit-logs-query.ts index 4480816c4a1..47f3cf7f372 100644 --- a/apps/studio/data/organizations/organization-audit-logs-query.ts +++ b/apps/studio/data/organizations/organization-audit-logs-query.ts @@ -1,5 +1,4 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' -import dayjs from 'dayjs' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' @@ -68,11 +67,12 @@ export const useOrganizationAuditLogsQuery = }: UseQueryOptions = {} ) => { const { slug, iso_timestamp_start, iso_timestamp_end } = vars - const date_start = dayjs(iso_timestamp_start).utc().format('YYYY-MM-DD') - const date_end = dayjs(iso_timestamp_end).utc().format('YYYY-MM-DD') return useQuery( - organizationKeys.auditLogs(slug, { date_start, date_end }), + organizationKeys.auditLogs(slug, { + date_start: iso_timestamp_start, + date_end: iso_timestamp_end, + }), ({ signal }) => getOrganizationAuditLogs(vars, signal), { enabled: enabled && typeof slug !== 'undefined',