From a9427f190a6c08d27158fa8471c1fd549c2ce619 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Tue, 17 Sep 2024 15:36:25 -0700 Subject: [PATCH] Extend time range (contributor submission) (#2484) * added new options for time range; removed duplicated code * refactor + remove unused code --------- Co-authored-by: Zoltan Szabo --- web/src/app/chat/modifiers/ChatFilters.tsx | 453 ------------------ web/src/app/config/timeRange.tsx | 9 + .../components/filters/TimeRangeSelector.tsx | 32 ++ .../components/search/DateRangeSelector.tsx | 77 +-- web/src/components/search/DocumentDisplay.tsx | 4 +- web/src/lib/dateUtils.ts | 7 + 6 files changed, 68 insertions(+), 514 deletions(-) delete mode 100644 web/src/app/chat/modifiers/ChatFilters.tsx create mode 100644 web/src/app/config/timeRange.tsx create mode 100644 web/src/components/filters/TimeRangeSelector.tsx diff --git a/web/src/app/chat/modifiers/ChatFilters.tsx b/web/src/app/chat/modifiers/ChatFilters.tsx deleted file mode 100644 index 61c45f12d..000000000 --- a/web/src/app/chat/modifiers/ChatFilters.tsx +++ /dev/null @@ -1,453 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import { DocumentSet, Tag, ValidSources } from "@/lib/types"; -import { SourceMetadata } from "@/lib/search/interfaces"; -import { - FiBook, - FiBookmark, - FiCalendar, - FiFilter, - FiMap, - FiTag, - FiX, -} from "react-icons/fi"; -import { DateRangePickerValue } from "@tremor/react"; -import { listSourceMetadata } from "@/lib/sources"; -import { SourceIcon } from "@/components/SourceIcon"; -import { BasicClickable } from "@/components/BasicClickable"; -import { ControlledPopup, DefaultDropdownElement } from "@/components/Dropdown"; -import { getXDaysAgo } from "@/lib/dateUtils"; -import { SourceSelectorProps } from "@/components/search/filtering/Filters"; -import { containsObject, objectsAreEquivalent } from "@/lib/contains"; - -enum FilterType { - Source = "Source", - KnowledgeSet = "Knowledge Set", - TimeRange = "Time Range", - Tag = "Tag", -} - -function SelectedBubble({ - children, - onClick, -}: { - children: string | JSX.Element; - onClick: () => void; -}) { - return ( -
- {children} - -
- ); -} - -function SelectFilterType({ - onSelect, - hasSources, - hasKnowledgeSets, - hasTags, -}: { - onSelect: (filterType: FilterType) => void; - hasSources: boolean; - hasKnowledgeSets: boolean; - hasTags: boolean; -}) { - return ( -
- {hasSources && ( - onSelect(FilterType.Source)} - isSelected={false} - /> - )} - - {hasKnowledgeSets && ( - onSelect(FilterType.KnowledgeSet)} - isSelected={false} - /> - )} - - {hasTags && ( - onSelect(FilterType.Tag)} - isSelected={false} - /> - )} - - onSelect(FilterType.TimeRange)} - isSelected={false} - /> -
- ); -} - -function SourcesSection({ - sources, - selectedSources, - onSelect, -}: { - sources: SourceMetadata[]; - selectedSources: string[]; - onSelect: (source: SourceMetadata) => void; -}) { - return ( -
- {sources.map((source) => ( - onSelect(source)} - isSelected={selectedSources.includes(source.internalName)} - includeCheckbox - /> - ))} -
- ); -} - -function KnowledgeSetsSection({ - documentSets, - selectedDocumentSets, - onSelect, -}: { - documentSets: DocumentSet[]; - selectedDocumentSets: string[]; - onSelect: (documentSetName: string) => void; -}) { - return ( -
- {documentSets.map((documentSet) => ( - onSelect(documentSet.name)} - isSelected={selectedDocumentSets.includes(documentSet.name)} - includeCheckbox - /> - ))} -
- ); -} - -const LAST_30_DAYS = "Last 30 days"; -const LAST_7_DAYS = "Last 7 days"; -const TODAY = "Today"; - -function TimeRangeSection({ - selectedTimeRange, - onSelect, -}: { - selectedTimeRange: string | null; - onSelect: (timeRange: DateRangePickerValue) => void; -}) { - return ( -
- - onSelect({ - to: new Date(), - from: getXDaysAgo(30), - selectValue: LAST_30_DAYS, - }) - } - isSelected={selectedTimeRange === LAST_30_DAYS} - /> - - - onSelect({ - to: new Date(), - from: getXDaysAgo(7), - selectValue: LAST_7_DAYS, - }) - } - isSelected={selectedTimeRange === LAST_7_DAYS} - /> - - - onSelect({ - to: new Date(), - from: getXDaysAgo(1), - selectValue: TODAY, - }) - } - isSelected={selectedTimeRange === TODAY} - /> -
- ); -} - -function TagsSection({ - availableTags, - selectedTags, - onSelect, -}: { - availableTags: Tag[]; - selectedTags: Tag[]; - onSelect: (tag: Tag) => void; -}) { - const [filterValue, setFilterValue] = useState(""); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - const filterValueLower = filterValue.toLowerCase(); - const filteredTags = filterValueLower - ? availableTags.filter( - (tags) => - tags.tag_value.toLowerCase().startsWith(filterValueLower) || - tags.tag_key.toLowerCase().startsWith(filterValueLower) - ) - : availableTags; - - return ( -
-
- {filteredTags.length > 0 ? ( - filteredTags.map((tag) => ( - - {tag.tag_key} - = - {tag.tag_value} -
- } - onSelect={() => onSelect(tag)} - isSelected={selectedTags.includes(tag)} - includeCheckbox - /> - )) - ) : ( -
No matching tags found
- )} -
- -
- setFilterValue(event.target.value)} - /> -
- - ); -} - -export function ChatFilters({ - timeRange, - setTimeRange, - selectedSources, - setSelectedSources, - selectedDocumentSets, - setSelectedDocumentSets, - selectedTags, - setSelectedTags, - availableDocumentSets, - existingSources, - availableTags, -}: SourceSelectorProps) { - const [filtersOpen, setFiltersOpen] = useState(false); - const handleFiltersToggle = (value: boolean) => { - setSelectedFilterType(null); - setFiltersOpen(value); - }; - const [selectedFilterType, setSelectedFilterType] = - useState(null); - - const handleSourceSelect = (source: SourceMetadata) => { - setSelectedSources((prev: SourceMetadata[]) => { - const prevSourceNames = prev.map((source) => source.internalName); - if (prevSourceNames.includes(source.internalName)) { - return prev.filter((s) => s.internalName !== source.internalName); - } else { - return [...prev, source]; - } - }); - }; - - const handleDocumentSetSelect = (documentSetName: string) => { - setSelectedDocumentSets((prev: string[]) => { - if (prev.includes(documentSetName)) { - return prev.filter((s) => s !== documentSetName); - } else { - return [...prev, documentSetName]; - } - }); - }; - - const handleTagToggle = (tag: Tag) => { - setSelectedTags((prev) => { - if (containsObject(prev, tag)) { - return prev.filter((t) => !objectsAreEquivalent(t, tag)); - } else { - return [...prev, tag]; - } - }); - }; - - const allSources = listSourceMetadata(); - const availableSources = allSources.filter((source) => - existingSources.includes(source.internalName) - ); - - let popupDisplay = null; - if (selectedFilterType === FilterType.Source) { - popupDisplay = ( - source.internalName)} - onSelect={handleSourceSelect} - /> - ); - } else if (selectedFilterType === FilterType.KnowledgeSet) { - popupDisplay = ( - - ); - } else if (selectedFilterType === FilterType.TimeRange) { - popupDisplay = ( - { - setTimeRange(timeRange); - handleFiltersToggle(!filtersOpen); - }} - /> - ); - } else if (selectedFilterType === FilterType.Tag) { - popupDisplay = ( - - ); - } else { - popupDisplay = ( - setSelectedFilterType(filterType)} - hasSources={availableSources.length > 0} - hasKnowledgeSets={availableDocumentSets.length > 0} - hasTags={availableTags.length > 0} - /> - ); - } - - return ( -
- -
- handleFiltersToggle(!filtersOpen)}> -
- Filter -
-
-
-
- -
- {((timeRange && timeRange.selectValue !== undefined) || - selectedSources.length > 0 || - selectedDocumentSets.length > 0) && ( -

Currently applied:

- )} -
- {timeRange && timeRange.selectValue && ( - setTimeRange(null)}> -
{timeRange.selectValue}
-
- )} - {existingSources.length > 0 && - selectedSources.map((source) => ( - handleSourceSelect(source)} - > - <> - - {source.displayName} - - - ))} - {selectedDocumentSets.length > 0 && - selectedDocumentSets.map((documentSetName) => ( - handleDocumentSetSelect(documentSetName)} - > - <> -
- -
- {documentSetName} - -
- ))} - - {selectedTags.length > 0 && - selectedTags.map((tag) => ( - handleTagToggle(tag)} - > - <> -
- -
- - {tag.tag_key} - = - {tag.tag_value} - - -
- ))} -
-
-
- ); -} diff --git a/web/src/app/config/timeRange.tsx b/web/src/app/config/timeRange.tsx new file mode 100644 index 000000000..ab7cf4768 --- /dev/null +++ b/web/src/app/config/timeRange.tsx @@ -0,0 +1,9 @@ +import { getXDaysAgo, getXYearsAgo } from "@/lib/dateUtils"; + +export const timeRangeValues = [ + { label: "Last 2 years", value: getXYearsAgo(2) }, + { label: "Last year", value: getXYearsAgo(1) }, + { label: "Last 30 days", value: getXDaysAgo(30) }, + { label: "Last 7 days", value: getXDaysAgo(7) }, + { label: "Today", value: getXDaysAgo(1) }, +]; diff --git a/web/src/components/filters/TimeRangeSelector.tsx b/web/src/components/filters/TimeRangeSelector.tsx new file mode 100644 index 000000000..7583a2fd8 --- /dev/null +++ b/web/src/components/filters/TimeRangeSelector.tsx @@ -0,0 +1,32 @@ +import { DefaultDropdownElement } from "../Dropdown"; +export function TimeRangeSelector({ + value, + onValueChange, + className, + timeRangeValues, +}: { + value: any; + onValueChange: any; + className: any; + + timeRangeValues: { label: string; value: Date }[]; +}) { + return ( +
+ {timeRangeValues.map((timeRangeValue) => ( + + onValueChange({ + to: new Date(), + from: timeRangeValue.value, + selectValue: timeRangeValue.label, + }) + } + isSelected={value?.selectValue === timeRangeValue.label} + /> + ))} +
+ ); +} diff --git a/web/src/components/search/DateRangeSelector.tsx b/web/src/components/search/DateRangeSelector.tsx index 13b42887b..dcbd5c1eb 100644 --- a/web/src/components/search/DateRangeSelector.tsx +++ b/web/src/components/search/DateRangeSelector.tsx @@ -1,11 +1,8 @@ -import { getXDaysAgo } from "@/lib/dateUtils"; import { DateRangePickerValue } from "@tremor/react"; import { FiCalendar, FiChevronDown, FiXCircle } from "react-icons/fi"; -import { CustomDropdown, DefaultDropdownElement } from "../Dropdown"; - -export const LAST_30_DAYS = "Last 30 days"; -export const LAST_7_DAYS = "Last 7 days"; -export const TODAY = "Today"; +import { CustomDropdown } from "../Dropdown"; +import { timeRangeValues } from "@/app/config/timeRange"; +import { TimeRangeSelector } from "@/components/filters/TimeRangeSelector"; export function DateRangeSelector({ value, @@ -20,59 +17,23 @@ export function DateRangeSelector({
- - onValueChange({ - to: new Date(), - from: getXDaysAgo(30), - selectValue: LAST_30_DAYS, - }) - } - isSelected={value?.selectValue === LAST_30_DAYS} - /> - - - onValueChange({ - to: new Date(), - from: getXDaysAgo(7), - selectValue: LAST_7_DAYS, - }) - } - isSelected={value?.selectValue === LAST_7_DAYS} - /> - - - onValueChange({ - to: new Date(), - from: getXDaysAgo(1), - selectValue: TODAY, - }) - } - isSelected={value?.selectValue === TODAY} - /> -
+ border + border-border + bg-background + rounded-lg + flex + flex-col + w-64 + max-h-96 + overflow-y-auto + flex + overscroll-contain`} + timeRangeValues={timeRangeValues} + onValueChange={onValueChange} + /> } >
{ const date = new Date(timestamp); const year = date.getFullYear();