diff --git a/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tab.tsx b/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tab.tsx index d17c0adf21..1a0a24f1e3 100644 --- a/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tab.tsx +++ b/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tab.tsx @@ -19,10 +19,10 @@ import { getPostDrivenTime } from "@/utils/questions/helpers"; import ContinuousAggregationChart from "./continuous_aggregations_chart"; import HistogramDrawer from "./histogram_drawer"; import { AGGREGATION_EXPLORER_OPTIONS } from "../constants"; -import { AggregationQuestionWithBots } from "../types"; +import { AggregationExtraQuestion } from "../types"; type Props = { - aggregationData: AggregationQuestionWithBots | null; + aggregationData: AggregationExtraQuestion | null; activeTab: string; selectedSubQuestionOption: number | string | null; postId: number; @@ -42,7 +42,6 @@ const AggregationsTab: FC = ({ const { aggregations, - bot_aggregations, actual_close_time, resolution, unit, @@ -54,11 +53,8 @@ const AggregationsTab: FC = ({ AGGREGATION_EXPLORER_OPTIONS[0]; const activeAggregation = useMemo( - () => - tabData?.includeBots - ? bot_aggregations?.[tabData.value] - : aggregations?.[tabData.value], - [aggregations, bot_aggregations, tabData] + () => aggregations?.[tabData.id], + [aggregations, tabData] ); let aggregationIndex: number | undefined; @@ -124,7 +120,7 @@ const AggregationsTab: FC = ({ return null; } - const renderAggregation = (questionData: AggregationQuestionWithBots) => { + const renderAggregation = (questionData: AggregationExtraQuestion) => { switch (questionData.type) { case QuestionType.Binary: return ( diff --git a/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tooltip.tsx b/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tooltip.tsx index 5778be7f23..687d17c99f 100644 --- a/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tooltip.tsx +++ b/front_end/src/app/(main)/aggregation-explorer/components/aggregation_tooltip.tsx @@ -8,28 +8,25 @@ import ChoiceCheckbox from "@/components/choice_checkbox"; import Button from "@/components/ui/button"; import LoadingIndicator from "@/components/ui/loading_indicator"; import { ChoiceItem } from "@/types/choices"; -import { AggregationMethod } from "@/types/question"; import { ThemeColor } from "@/types/theme"; import { logError } from "@/utils/core/errors"; -import { AggregationMethodWithBots } from "../types"; +import { AggregationExtraMethod } from "../types"; type Props = { valueLabel: string; tooltips: { - aggregationMethod: AggregationMethod; - choice: AggregationMethodWithBots; + aggregationMethod: string; + choice: AggregationExtraMethod; label: string; includeBots: boolean; color: ThemeColor; }; - onFetchData: ( - aggregationOptionId: AggregationMethodWithBots - ) => Promise; + onFetchData: (aggregationOptionId: AggregationExtraMethod) => Promise; onChoiceChange: (choice: string, checked: boolean) => void; onChoiceHighlight: (choice: string, highlighted: boolean) => void; choiceItems: ChoiceItem[]; - onTabChange: (activeTab: AggregationMethodWithBots) => void; + onTabChange: (activeTab: AggregationExtraMethod) => void; }; const AggregationTooltip: FC = ({ diff --git a/front_end/src/app/(main)/aggregation-explorer/components/aggregation_wrapper.tsx b/front_end/src/app/(main)/aggregation-explorer/components/aggregation_wrapper.tsx index 043e406788..a1e5908e65 100644 --- a/front_end/src/app/(main)/aggregation-explorer/components/aggregation_wrapper.tsx +++ b/front_end/src/app/(main)/aggregation-explorer/components/aggregation_wrapper.tsx @@ -8,16 +8,14 @@ import { logError } from "@/utils/core/errors"; import AggregationsTab from "./aggregation_tab"; import AggregationsDrawer from "./aggregations_drawer"; import { AGGREGATION_EXPLORER_OPTIONS } from "../constants"; -import { - AggregationMethodWithBots, - AggregationQuestionWithBots, -} from "../types"; +import { AggregationExtraMethod, AggregationExtraQuestion } from "../types"; type Props = { - activeTab: AggregationMethodWithBots | null; - onTabChange: (activeTab: AggregationMethodWithBots) => void; + activeTab: AggregationExtraMethod | null; + onTabChange: (activeTab: AggregationExtraMethod) => void; data: QuestionWithForecasts | PostWithForecasts; selectedSubQuestionOption: number | string | null; + joinedBeforeDate?: string; additionalParams?: { userIds?: number[]; // Array of user IDs as a comma-separated string }; @@ -28,22 +26,27 @@ export const AggregationWrapper: FC = ({ onTabChange, selectedSubQuestionOption, data, + joinedBeforeDate, additionalParams = {}, }) => { const postId = "post_id" in data ? data.post_id : data.id; const [selectedAggregationMethods, setSelectedAggregationMethods] = useState< - AggregationMethodWithBots[] + AggregationExtraMethod[] >([]); const [aggregationData, setAggregationData] = - useState(null); + useState(null); const handleFetchAggregations = useCallback( - async (aggregationOptionId: AggregationMethodWithBots) => { + async (aggregationOptionId: AggregationExtraMethod) => { const aggregationOption = AGGREGATION_EXPLORER_OPTIONS.find( (option) => option.id === aggregationOptionId ) ?? AGGREGATION_EXPLORER_OPTIONS[0]; - const { value: aggregationMethod, includeBots } = aggregationOption; + const { + value: methodName, + id: methodID, + includeBots, + } = aggregationOption; if (selectedAggregationMethods.includes(aggregationOptionId)) { return; @@ -60,46 +63,23 @@ export const AggregationWrapper: FC = ({ postId, questionId: adjustedQuestionId, includeBots, - aggregationMethods: aggregationMethod, + aggregationMethods: methodName, + joinedBeforeDate, ...additionalParams, }); - const fetchedAggregationData = response.aggregations[aggregationMethod]; + const fetchedAggregationData = response.aggregations[methodName]; if (fetchedAggregationData !== undefined) { - setAggregationData((prev) => - prev - ? ({ - ...prev, - ...(includeBots - ? { - bot_aggregations: { - ...prev.bot_aggregations, - [aggregationMethod]: fetchedAggregationData, - }, - } - : { - aggregations: { - ...prev.aggregations, - [aggregationMethod]: fetchedAggregationData, - }, - }), - } as AggregationQuestionWithBots) - : ({ - ...response, - ...(includeBots - ? { - bot_aggregations: { - [aggregationMethod]: fetchedAggregationData, - }, - aggregations: {}, - } - : { - aggregations: { - [aggregationMethod]: fetchedAggregationData, - }, - }), - } as AggregationQuestionWithBots) - ); + setAggregationData((prev) => { + const base = prev ?? response; + return { + ...base, + aggregations: { + ...base.aggregations, + [methodID]: fetchedAggregationData, + }, + } as AggregationExtraQuestion; + }); } setSelectedAggregationMethods((prev) => [...prev, aggregationOptionId]); } catch (err) { @@ -110,6 +90,7 @@ export const AggregationWrapper: FC = ({ selectedAggregationMethods, selectedSubQuestionOption, postId, + joinedBeforeDate, additionalParams, ] ); @@ -129,6 +110,7 @@ export const AggregationWrapper: FC = ({ onFetchData={handleFetchAggregations} aggregationData={aggregationData} selectedSubQuestionOption={selectedSubQuestionOption} + joinedBeforeDate={joinedBeforeDate} /> ); }; diff --git a/front_end/src/app/(main)/aggregation-explorer/components/aggregations_drawer.tsx b/front_end/src/app/(main)/aggregation-explorer/components/aggregations_drawer.tsx index 232ecbf4a4..7802752be7 100644 --- a/front_end/src/app/(main)/aggregation-explorer/components/aggregations_drawer.tsx +++ b/front_end/src/app/(main)/aggregation-explorer/components/aggregations_drawer.tsx @@ -27,18 +27,14 @@ import { generateAggregationTooltips, generateChoiceItemsFromAggregations, } from "../helpers"; -import { - AggregationMethodWithBots, - AggregationQuestionWithBots, -} from "../types"; +import { AggregationExtraMethod, AggregationExtraQuestion } from "../types"; type Props = { - onTabChange: (activeTab: AggregationMethodWithBots) => void; - onFetchData: ( - aggregationOptionId: AggregationMethodWithBots - ) => Promise; - aggregationData: AggregationQuestionWithBots | null; + onTabChange: (activeTab: AggregationExtraMethod) => void; + onFetchData: (aggregationOptionId: AggregationExtraMethod) => Promise; + aggregationData: AggregationExtraQuestion | null; selectedSubQuestionOption: number | string | null; + joinedBeforeDate?: string; }; const AggregationsDrawer: FC = ({ @@ -46,6 +42,7 @@ const AggregationsDrawer: FC = ({ onFetchData, aggregationData, selectedSubQuestionOption, + joinedBeforeDate, }) => { const { user } = useAuth(); const { actual_close_time, scaling, type, actual_resolve_time } = @@ -54,7 +51,10 @@ const AggregationsDrawer: FC = ({ () => getPostDrivenTime(actual_close_time), [actual_close_time] ); - const tooltips = useMemo(() => generateAggregationTooltips(user), [user]); + const tooltips = useMemo( + () => generateAggregationTooltips(user, joinedBeforeDate), + [joinedBeforeDate, user] + ); const [choiceItems, setChoiceItems] = useState( aggregationData ? generateChoiceItemsFromAggregations({ diff --git a/front_end/src/app/(main)/aggregation-explorer/components/continuous_aggregations_chart.tsx b/front_end/src/app/(main)/aggregation-explorer/components/continuous_aggregations_chart.tsx index 4d5ad23e48..22940967bd 100644 --- a/front_end/src/app/(main)/aggregation-explorer/components/continuous_aggregations_chart.tsx +++ b/front_end/src/app/(main)/aggregation-explorer/components/continuous_aggregations_chart.tsx @@ -21,10 +21,10 @@ import { import { cdfToPmf } from "@/utils/math"; import { formatValueUnit } from "@/utils/questions/units"; -import { AggregationQuestionWithBots } from "../types"; +import { AggregationExtraQuestion } from "../types"; type Props = { - questionData: AggregationQuestionWithBots; + questionData: AggregationExtraQuestion; activeAggregation: AggregateForecastHistory; selectedTimestamp: number | null; }; diff --git a/front_end/src/app/(main)/aggregation-explorer/components/explorer.tsx b/front_end/src/app/(main)/aggregation-explorer/components/explorer.tsx index 9fbbb1dcdc..5d20a5c6f7 100644 --- a/front_end/src/app/(main)/aggregation-explorer/components/explorer.tsx +++ b/front_end/src/app/(main)/aggregation-explorer/components/explorer.tsx @@ -26,7 +26,7 @@ import { logError } from "@/utils/core/errors"; import { parseQuestionId } from "@/utils/questions/helpers"; import { AggregationWrapper } from "./aggregation_wrapper"; -import { AggregationMethodWithBots } from "../types"; +import { AggregationExtraMethod } from "../types"; function sanitizeUserIds(input: string): number[] { if (!input) return []; @@ -59,7 +59,7 @@ const Explorer: FC = ({ searchParams }) => { string | number | null >(parseSubQuestionOption(question_id, option)); - const [activeTab, setActiveTab] = useState( + const [activeTab, setActiveTab] = useState( null ); const [postInputText, setPostInputText] = useState( @@ -75,6 +75,11 @@ const Explorer: FC = ({ searchParams }) => { : searchParams.user_ids )?.toString() || "" ); + const [joinedBeforeDate, setJoinedBeforeDate] = useState( + (Array.isArray(searchParams.joined_before_date) + ? searchParams.joined_before_date[0] + : searchParams.joined_before_date) || "" + ); // clear subquestion options when post id input changes useEffect(() => { @@ -170,6 +175,10 @@ const Explorer: FC = ({ searchParams }) => { params.set("user_ids", cleanedIds.join(",")); } + if (joinedBeforeDate.trim()) { + params.set("joined_before_date", joinedBeforeDate); + } + router.push(`/aggregation-explorer?${params.toString()}`); }; @@ -213,6 +222,7 @@ const Explorer: FC = ({ searchParams }) => { onTabChange={setActiveTab} data={data} selectedSubQuestionOption={selectedSubQuestionOption} + joinedBeforeDate={joinedBeforeDate || undefined} additionalParams={{ userIds: sanitizeUserIds(userIdsText) }} /> @@ -291,7 +301,7 @@ const Explorer: FC = ({ searchParams }) => { value={selectedSubQuestionOption} onChange={handleSubQuestionSelectChange} /> - {user?.is_staff && ( + { // TODO: move "include bots" to here instead of in each tab // user ids should only be avilable to staff or whitelisted users // copy logic and parameters from the download data modal @@ -299,26 +309,42 @@ const Explorer: FC = ({ searchParams }) => {
+ {user?.is_staff && ( +
+ + setUserIdsText(e.target.value)} + className="w-full cursor-default overflow-hidden rounded border border-gray-500 bg-white p-2 text-left text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 dark:bg-blue-950 dark:text-gray-200 sm:text-sm" + /> +
+ )} +
setUserIdsText(e.target.value)} + name="joined_before_date" + type="date" + value={joinedBeforeDate} + onChange={(e) => setJoinedBeforeDate(e.target.value)} className="w-full cursor-default overflow-hidden rounded border border-gray-500 bg-white p-2 text-left text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 dark:bg-blue-950 dark:text-gray-200 sm:text-sm" />
- )} + }