Skip to content

Commit ad2672b

Browse files
committed
Merge remote-tracking branch 'origin/deploy/konwledge' into deploy/konwledge
# Conflicts: # web/features/new-rag/overview/overview-activity.tsx
2 parents 7fb2f93 + 5f2edec commit ad2672b

3 files changed

Lines changed: 119 additions & 10 deletions

File tree

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

Lines changed: 90 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,86 @@ 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 ordinal = (day: number) => {
862+
const suffix =
863+
['th', 'st', 'nd', 'rd'][day % 100 > 10 && day % 100 < 14 ? 0 : day % 10] ?? 'th'
864+
return `${day}${suffix}`
865+
}
866+
const calendarLabel = (date: dayjs.Dayjs) =>
867+
new RegExp(`${date.format('MMMM')} ${ordinal(date.date())}, ${date.year()}`)
868+
await user.click(
869+
screen.getByRole('button', { name: /knowledgeOverview\.overview\.timeRange start/ }),
870+
)
871+
expect(
872+
screen.getByRole('button', { name: calendarLabel(earliest.subtract(1, 'day')) }),
873+
).toBeDisabled()
874+
expect(screen.getByRole('button', { name: calendarLabel(earliest) })).toBeEnabled()
875+
await user.click(screen.getByRole('button', { name: calendarLabel(earliest.add(1, 'day')) }))
876+
expect(
877+
screen.getByRole('combobox', { name: 'knowledgeOverview.overview.timeRange' }),
878+
).toHaveTextContent('filter.period.custom')
879+
expect(infiniteOptionsMocks.activity.mock.lastCall?.[0].input(null).query.from_at).toBe(
880+
earliest.add(1, 'day').toISOString(),
881+
)
882+
})
883+
884+
it.each(['COMMUNITY', 'ENTERPRISE'])(
885+
'preserves all-time activity queries for %s',
886+
async (edition) => {
887+
editionMock.value = edition
888+
const user = userEvent.setup()
889+
renderOverviewWithNuqs(<KnowledgeOverviewPage knowledgeSpaceId="space-1" />)
890+
expect(queryOptionsMocks.activity.mock.lastCall?.[0].input.query).not.toHaveProperty(
891+
'from_at',
892+
)
893+
await user.click(
894+
screen.getByRole('button', { name: 'knowledgeOverview.overview.allActivity' }),
895+
)
896+
await user.click(
897+
screen.getByRole('combobox', { name: 'knowledgeOverview.overview.timeRange' }),
898+
)
899+
await user.click(
900+
await screen.findByRole('option', { name: 'knowledgeOverview.overview.allTime' }),
901+
)
902+
903+
const options = infiniteOptionsMocks.activity.mock.lastCall?.[0] as {
904+
input: (cursor: string | null) => { query: Record<string, unknown> }
905+
}
906+
expect(options.input(null).query).not.toHaveProperty('from_at')
907+
expect(options.input(null).query).not.toHaveProperty('to_at')
908+
},
909+
)
910+
824911
it('shows safe activity details and relative times for today in the drawer', async () => {
825912
// At midnight, "two hours ago" is yesterday and deliberately uses an absolute date.
826913
vi.useFakeTimers({ toFake: ['Date'] })

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ 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 {
@@ -37,6 +37,7 @@ import {
3737
DatePickerTrigger,
3838
DatePickerValue,
3939
} from '@/app/components/base/date-time-picker/date-picker'
40+
import { deploymentEditionAtom } from '@/features/system-features/state'
4041
import { consoleQuery } from '@/service/console'
4142
import { useMembers } from '@/service/use-common'
4243
import { activityDatesForRange } from './overview-activity-types'
@@ -344,9 +345,11 @@ export function OverviewActivity() {
344345

345346
function ActivityDateRangePicker({
346347
dates,
348+
minDate,
347349
onChange,
348350
}: {
349351
dates: ActivityDateRange
352+
minDate?: ActivityDateRange['start']
350353
onChange: (dates: ActivityDateRange) => void
351354
}) {
352355
const { t, i18n } = useTranslation(['knowledgeOverview'])
@@ -358,6 +361,7 @@ function ActivityDateRangePicker({
358361
const timeRangeLabel = t(($) => $['overview.timeRange'], { ns: 'knowledgeOverview' })
359362
const start = dates.start.format('YYYY-MM-DD')
360363
const end = dates.end.format('YYYY-MM-DD')
364+
const minDay = minDate?.format('YYYY-MM-DD')
361365
const triggerClassName =
362366
'h-5 w-auto min-w-0 flex-1 truncate rounded px-1 system-xs-regular text-components-input-text-filled'
363367

@@ -369,6 +373,7 @@ function ActivityDateRangePicker({
369373
>
370374
<DatePicker
371375
value={start}
376+
minDate={minDay}
372377
maxDate={[end, today].sort()[0]}
373378
onValueChange={(next) => {
374379
if (next) onChange({ end: dates.end, start: dayjs(next).startOf('day') })
@@ -385,7 +390,7 @@ function ActivityDateRangePicker({
385390
</span>
386391
<DatePicker
387392
value={end}
388-
minDate={start}
393+
minDate={minDay && minDay > start ? minDay : start}
389394
maxDate={today}
390395
onValueChange={(next) => {
391396
if (next) onChange({ end: dayjs(next).endOf('day'), start: dates.start })
@@ -411,13 +416,20 @@ function ActivityDrawer({
411416
const { t, i18n } = useTranslation(['knowledgeSpace', 'knowledgeOverview', 'knowledgeTasks'])
412417
const { t: tCommon } = useTranslation(['common'])
413418
const { t: tActivityLog } = useTranslation(['appLog'])
419+
const isCloudEdition = useAtomValue(deploymentEditionAtom) === 'CLOUD'
414420
const knowledgeSpaceId = useAtomValueRawSync(overviewKnowledgeSpaceIdAtom)
415421
const members = useMembers().data?.accounts ?? []
416422
const [range, setRange] = useState<ActivityRange>('today')
417423
const [dates, setDates] = useState<ActivityDateRange>(() => activityDatesForRange('today'))
418424
const [operator, setOperator] = useState<ActivityOperator>('all')
419-
const activityFrom = range === 'all' ? undefined : dates.start.toISOString()
420-
const activityTo = range === 'all' ? undefined : dates.end.toISOString()
425+
const minDate = isCloudEdition ? activityDatesForRange('90d').start : undefined
426+
const activityStart = minDate && dates.start.isBefore(minDate) ? minDate : dates.start
427+
const activityEnd = minDate && dates.end.isBefore(minDate) ? minDate.endOf('day') : dates.end
428+
const activityFrom = range === 'all' && !isCloudEdition ? undefined : activityStart.toISOString()
429+
const activityTo = range === 'all' && !isCloudEdition ? undefined : activityEnd.toISOString()
430+
const activityRanges = isCloudEdition
431+
? ACTIVITY_RANGES.filter((value) => value !== 'all')
432+
: ACTIVITY_RANGES
421433
const activityActorType =
422434
operator === 'all' ? undefined : operator === 'system' ? 'system' : 'member'
423435
const activityActorId = operator.startsWith('member:')
@@ -570,7 +582,7 @@ function ActivityDrawer({
570582
<span className="truncate">{rangeTriggerLabel[range]}</span>
571583
</SelectTrigger>
572584
<SelectContent>
573-
{ACTIVITY_RANGES.map((value) => (
585+
{activityRanges.map((value) => (
574586
<SelectItem key={value} value={value}>
575587
<SelectItemText>{rangeLabel[value]}</SelectItemText>
576588
<SelectItemIndicator />
@@ -583,7 +595,11 @@ function ActivityDrawer({
583595
{rangeLabel.all}
584596
</div>
585597
) : (
586-
<ActivityDateRangePicker dates={dates} onChange={handleDatesChange} />
598+
<ActivityDateRangePicker
599+
dates={dates}
600+
minDate={minDate}
601+
onChange={handleDatesChange}
602+
/>
587603
)}
588604
<Select
589605
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)