Skip to content

Commit 5f2edec

Browse files
committed
fix(web): limit cloud knowledge activity to recent 90 days
1 parent 971dab3 commit 5f2edec

3 files changed

Lines changed: 121 additions & 11 deletions

File tree

‎web/features/new-rag/overview/__tests__/page.spec.tsx‎

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const infiniteOptionsMocks = vi.hoisted(() => ({
3030

3131
const chartOptions = vi.hoisted(() => ({ current: undefined as unknown }))
3232

33+
const editionMock = vi.hoisted(() => ({ value: 'COMMUNITY' }))
34+
3335
const queryData = vi.hoisted(() => ({
3436
activity: {
3537
data: [
@@ -190,9 +192,13 @@ vi.mock('@/context/permission-state', () => ({
190192
workspacePermissionKeysAtom: permissionState.workspaceKeysAtom,
191193
}))
192194

193-
vi.mock('@/features/system-features/state', () => ({
194-
knowledgeFsUploadEnabledAtom: systemFeaturesState.uploadAvailableAtom,
195-
}))
195+
vi.mock('@/features/system-features/state', async () => {
196+
const { atom } = await import('jotai')
197+
return {
198+
deploymentEditionAtom: atom(() => editionMock.value),
199+
knowledgeFsUploadEnabledAtom: systemFeaturesState.uploadAvailableAtom,
200+
}
201+
})
196202

197203
vi.mock('../../space/context', () => ({
198204
useKnowledgeSpacePermission: (permission: string) =>
@@ -367,6 +373,7 @@ function renderOverviewWithNuqs(
367373
describe('KnowledgeOverviewPage', () => {
368374
beforeEach(() => {
369375
vi.clearAllMocks()
376+
editionMock.value = 'COMMUNITY'
370377
tasksQueryState.isError = false
371378
tasksQueryState.isPending = false
372379
tasksQueryState.isRefetching = false
@@ -821,6 +828,83 @@ describe('KnowledgeOverviewPage', () => {
821828
)
822829
})
823830

831+
it('limits cloud activity filters to the most recent 90 calendar days', async () => {
832+
vi.useFakeTimers({ toFake: ['Date'] })
833+
vi.setSystemTime(new Date(2026, 8, 8, 12))
834+
editionMock.value = 'CLOUD'
835+
const user = userEvent.setup()
836+
const earliest = dayjs().subtract(89, 'day').startOf('day')
837+
838+
renderOverviewWithNuqs(<KnowledgeOverviewPage knowledgeSpaceId="space-1" />)
839+
expect(queryOptionsMocks.activity.mock.lastCall?.[0]).toMatchObject({
840+
input: { query: { from_at: earliest.toISOString() } },
841+
})
842+
await user.click(screen.getByRole('button', { name: 'knowledgeOverview.overview.allActivity' }))
843+
await user.click(screen.getByRole('combobox', { name: 'knowledgeOverview.overview.timeRange' }))
844+
expect(
845+
screen.queryByRole('option', { name: 'knowledgeOverview.overview.allTime' }),
846+
).not.toBeInTheDocument()
847+
await user.click(
848+
await screen.findByRole('option', { name: 'knowledgeOverview.overview.last90Days' }),
849+
)
850+
851+
const options = infiniteOptionsMocks.activity.mock.lastCall?.[0] as {
852+
input: (cursor: string | null) => { query: Record<string, unknown> }
853+
}
854+
expect(options.input(null).query.from_at).toBe(earliest.toISOString())
855+
expect(options.input('older').query).toMatchObject({
856+
cursor: 'older',
857+
from_at: earliest.toISOString(),
858+
to_at: dayjs().endOf('day').toISOString(),
859+
})
860+
861+
const calendarLabel = (date: dayjs.Dayjs) =>
862+
new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeZone: 'UTC' }).format(
863+
new Date(Date.UTC(date.year(), date.month(), date.date())),
864+
)
865+
await user.click(
866+
screen.getByRole('button', { name: 'knowledgeOverview.overview.timeRange start' }),
867+
)
868+
expect(
869+
screen.getByRole('button', { name: calendarLabel(earliest.subtract(1, 'day')) }),
870+
).toBeDisabled()
871+
expect(screen.getByRole('button', { name: calendarLabel(earliest) })).toBeEnabled()
872+
await user.click(screen.getByRole('button', { name: calendarLabel(earliest.add(1, 'day')) }))
873+
expect(
874+
screen.getByRole('combobox', { name: 'knowledgeOverview.overview.timeRange' }),
875+
).toHaveTextContent('filter.period.custom')
876+
expect(infiniteOptionsMocks.activity.mock.lastCall?.[0].input(null).query.from_at).toBe(
877+
earliest.add(1, 'day').toISOString(),
878+
)
879+
})
880+
881+
it.each(['COMMUNITY', 'ENTERPRISE'])(
882+
'preserves all-time activity queries for %s',
883+
async (edition) => {
884+
editionMock.value = edition
885+
const user = userEvent.setup()
886+
renderOverviewWithNuqs(<KnowledgeOverviewPage knowledgeSpaceId="space-1" />)
887+
expect(queryOptionsMocks.activity.mock.lastCall?.[0].input.query).not.toHaveProperty(
888+
'from_at',
889+
)
890+
await user.click(
891+
screen.getByRole('button', { name: 'knowledgeOverview.overview.allActivity' }),
892+
)
893+
await user.click(
894+
screen.getByRole('combobox', { name: 'knowledgeOverview.overview.timeRange' }),
895+
)
896+
await user.click(
897+
await screen.findByRole('option', { name: 'knowledgeOverview.overview.allTime' }),
898+
)
899+
900+
const options = infiniteOptionsMocks.activity.mock.lastCall?.[0] as {
901+
input: (cursor: string | null) => { query: Record<string, unknown> }
902+
}
903+
expect(options.input(null).query).not.toHaveProperty('from_at')
904+
expect(options.input(null).query).not.toHaveProperty('to_at')
905+
},
906+
)
907+
824908
it('shows safe activity details and relative times for today in the drawer', async () => {
825909
// At midnight, "two hours ago" is yesterday and deliberately uses an absolute date.
826910
vi.useFakeTimers({ toFake: ['Date'] })

‎web/features/new-rag/overview/overview-activity.tsx‎

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ import {
2727
} from '@langgenius/dify-ui/select'
2828
import { useInfiniteQuery } from '@tanstack/react-query'
2929
import dayjs from 'dayjs'
30-
import { useAtomValueRawSync, useSetAtom } from 'jotai'
30+
import { useAtomValue, useAtomValueRawSync, useSetAtom } from 'jotai'
3131
import { useEffect, useMemo, useRef, useState } from 'react'
3232
import { useTranslation } from 'react-i18next'
3333
import DatePicker from '@/app/components/base/date-and-time-picker/date-picker'
34+
import { deploymentEditionAtom } from '@/features/system-features/state'
3435
import { consoleQuery } from '@/service/console'
3536
import { useMembers } from '@/service/use-common'
3637
import { activityDatesForRange } from './overview-activity-types'
@@ -338,9 +339,11 @@ export function OverviewActivity() {
338339

339340
function ActivityDateRangePicker({
340341
dates,
342+
minDate,
341343
onChange,
342344
}: {
343345
dates: ActivityDateRange
346+
minDate?: ActivityDateRange['start']
344347
onChange: (dates: ActivityDateRange) => void
345348
}) {
346349
const { t, i18n } = useTranslation(['knowledgeOverview'])
@@ -387,7 +390,11 @@ function ActivityDateRangePicker({
387390
onChange={(start) => start && onChange({ end: dates.end, start: start.startOf('day') })}
388391
onClear={() => undefined}
389392
renderTrigger={renderTrigger('start')}
390-
getIsDateDisabled={(date) => date.isAfter(today, 'day') || date.isAfter(dates.end, 'day')}
393+
getIsDateDisabled={(date) =>
394+
date.isAfter(today, 'day') ||
395+
date.isAfter(dates.end, 'day') ||
396+
(!!minDate && date.isBefore(minDate, 'day'))
397+
}
391398
/>
392399
<span aria-hidden className="text-text-quaternary">
393400
–
@@ -400,7 +407,9 @@ function ActivityDateRangePicker({
400407
onClear={() => undefined}
401408
renderTrigger={renderTrigger('end')}
402409
getIsDateDisabled={(date) =>
403-
date.isAfter(today, 'day') || date.isBefore(dates.start, 'day')
410+
date.isAfter(today, 'day') ||
411+
date.isBefore(dates.start, 'day') ||
412+
(!!minDate && date.isBefore(minDate, 'day'))
404413
}
405414
/>
406415
</div>
@@ -417,13 +426,20 @@ function ActivityDrawer({
417426
const { t, i18n } = useTranslation(['knowledgeSpace', 'knowledgeOverview', 'knowledgeTasks'])
418427
const { t: tCommon } = useTranslation(['common'])
419428
const { t: tActivityLog } = useTranslation(['appLog'])
429+
const isCloudEdition = useAtomValue(deploymentEditionAtom) === 'CLOUD'
420430
const knowledgeSpaceId = useAtomValueRawSync(overviewKnowledgeSpaceIdAtom)
421431
const members = useMembers().data?.accounts ?? []
422432
const [range, setRange] = useState<ActivityRange>('today')
423433
const [dates, setDates] = useState<ActivityDateRange>(() => activityDatesForRange('today'))
424434
const [operator, setOperator] = useState<ActivityOperator>('all')
425-
const activityFrom = range === 'all' ? undefined : dates.start.toISOString()
426-
const activityTo = range === 'all' ? undefined : dates.end.toISOString()
435+
const minDate = isCloudEdition ? activityDatesForRange('90d').start : undefined
436+
const activityStart = minDate && dates.start.isBefore(minDate) ? minDate : dates.start
437+
const activityEnd = minDate && dates.end.isBefore(minDate) ? minDate.endOf('day') : dates.end
438+
const activityFrom = range === 'all' && !isCloudEdition ? undefined : activityStart.toISOString()
439+
const activityTo = range === 'all' && !isCloudEdition ? undefined : activityEnd.toISOString()
440+
const activityRanges = isCloudEdition
441+
? ACTIVITY_RANGES.filter((value) => value !== 'all')
442+
: ACTIVITY_RANGES
427443
const activityActorType =
428444
operator === 'all' ? undefined : operator === 'system' ? 'system' : 'member'
429445
const activityActorId = operator.startsWith('member:')
@@ -572,7 +588,7 @@ function ActivityDrawer({
572588
<span className="truncate">{rangeTriggerLabel[range]}</span>
573589
</SelectTrigger>
574590
<SelectContent>
575-
{ACTIVITY_RANGES.map((value) => (
591+
{activityRanges.map((value) => (
576592
<SelectItem key={value} value={value}>
577593
<SelectItemText>{rangeLabel[value]}</SelectItemText>
578594
<SelectItemIndicator />
@@ -585,7 +601,11 @@ function ActivityDrawer({
585601
{rangeLabel.all}
586602
</div>
587603
) : (
588-
<ActivityDateRangePicker dates={dates} onChange={handleDatesChange} />
604+
<ActivityDateRangePicker
605+
dates={dates}
606+
minDate={minDate}
607+
onChange={handleDatesChange}
608+
/>
589609
)}
590610
<Select
591611
value={operator}

‎web/features/new-rag/overview/state.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { atomWithInfiniteQuery, atomWithQuery, queryClientAtom } from 'jotai-tan
55
import { atomWithLazy, selectAtom } from 'jotai/utils'
66
import { deploymentEditionAtom } from '@/features/system-features/state'
77
import { consoleQuery } from '@/service/console'
8+
import { activityDatesForRange } from './overview-activity-types'
89
import { OVERVIEW_REFRESH_INTERVAL, overviewRefreshInterval } from './overview-format'
910
import { overviewQueryGroup } from './query-state'
1011

@@ -183,7 +184,12 @@ const activityPreviewQueryAtom = atomWithQuery((get) =>
183184
consoleQuery.knowledgeFs.spaces.byControlSpaceId.overview.activity.get.queryOptions({
184185
input: {
185186
params: { control_space_id: get(overviewKnowledgeSpaceIdAtom) },
186-
query: { limit: ACTIVITY_PREVIEW_PAGE_SIZE },
187+
query: {
188+
limit: ACTIVITY_PREVIEW_PAGE_SIZE,
189+
...(get(deploymentEditionAtom) === 'CLOUD'
190+
? { from_at: activityDatesForRange('90d').start.toISOString() }
191+
: {}),
192+
},
187193
},
188194
refetchInterval: get(overviewHasActiveTasksAtom) ? OVERVIEW_REFRESH_INTERVAL : false,
189195
}),

0 commit comments

Comments
 (0)