Skip to content

Commit 5098456

Browse files
authored
[OPIK-3506] [FE] Resolve UX feedback for Dashboard beta release (#4484)
* [OPIK-3506] [FE] Update dashboard UX text labels - Change widget tooltip to "Using the dashboard's default experiment settings" - Update select experiments description for clarity - Rename "Global dashboard configuration" to "Dashboard defaults" - Simplify dashboard defaults description * Revision 2: Update project widget text labels for consistency - Change project info message to "Using the dashboard's default project settings" - Update project selector description to match experiments widget terminology - Ensures consistent UX language across all dashboard widgets * Revision 3: Show experiment count instead of names in select - Display "X experiments" when multiple experiments are selected - Add tooltip showing all experiment names on hover - Keep single experiment name display for clarity - Improves UX by preventing overflow in dashboard creation dialog * Revision 4: Change Select experiments icon to ListChecks - Replace ListFilter icon with ListChecks for better clarity - ListChecks better represents selecting items from a list - Filter icon remains for Filter and group option * Revision 7: Clear validation errors when changing template - Add form.clearErrors() when selecting a new template - Fixes issue where name field stays red after selecting different template - Improves UX by resetting validation state on template change * Revision 8: Update data source terminology for clarity - Change "Filter and group" to "Filter experiments" - Change "Select experiments" to "Manual selection" - Improves UX with clearer, more descriptive terminology * Revision 9: Improve filter/group section layout - Add single description above both Filters and Group by sections - Add hideBorder prop to FiltersAccordionSection and GroupsAccordionSection - Remove Label wrapper from accordion triggers for cleaner text - Use hideBorder={true} on GroupsAccordionSection to avoid double borders - Add mb-2 to description for proper spacing * Revision 10: Update dashboard select dropdown styling
1 parent f9a470a commit 5098456

File tree

10 files changed

+62
-40
lines changed

10 files changed

+62
-40
lines changed

apps/opik-frontend/src/components/pages-shared/dashboards/AddEditCloneDashboardDialog/AddEditCloneDashboardDialog.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ const AddEditCloneDashboardDialog: React.FC<
158158
shouldDirty: true,
159159
shouldTouch: true,
160160
});
161+
162+
form.clearErrors();
161163
};
162164

163165
const handleBack = () => {

apps/opik-frontend/src/components/pages-shared/dashboards/DashboardProjectSettingsButton/DashboardProjectSettingsButton.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const DashboardProjectSettingsButton: React.FC = () => {
5050

5151
return (
5252
<Popover>
53-
<TooltipWrapper content="Global dashboard configuration">
53+
<TooltipWrapper content="Dashboard defaults">
5454
<PopoverTrigger asChild>
5555
<Button size="icon-sm" variant="outline">
5656
<Settings className="size-3.5" />
@@ -60,13 +60,10 @@ const DashboardProjectSettingsButton: React.FC = () => {
6060
<PopoverContent align="end" className="w-80">
6161
<div className="space-y-4">
6262
<div>
63-
<h3 className="comet-title-s mb-2">
64-
Global dashboard configuration
65-
</h3>
63+
<h3 className="comet-title-s mb-2">Dashboard defaults</h3>
6664
<Description>
6765
Set the default project and experiments for all widgets.
68-
Individual widgets can override these settings with their own
69-
configuration.
66+
Individual widgets can override these settings if needed.
7067
</Description>
7168
</div>
7269
<div>

apps/opik-frontend/src/components/pages-shared/dashboards/DashboardSelectBox/SelectItem.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ export const SelectItem: React.FC<SelectItemProps> = ({
5656
return (
5757
<DropdownMenuItem
5858
className={cn(
59-
"min-h-12 cursor-pointer items-start gap-2 py-2 pl-6",
59+
"min-h-12 cursor-pointer items-start gap-2 py-2 pl-8",
6060
isSelected && "bg-primary-foreground",
6161
)}
6262
onSelect={() => onSelect(id)}
6363
>
6464
{isSelected && (
6565
<Check
66-
className="absolute left-1 top-2.5 size-3.5 text-foreground"
66+
className="absolute left-2 top-2.5 size-3.5 text-muted-slate"
6767
strokeWidth="3"
6868
/>
6969
)}
@@ -74,7 +74,7 @@ export const SelectItem: React.FC<SelectItemProps> = ({
7474
)}
7575
/>
7676
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
77-
<div className="comet-body-s truncate">{name}</div>
77+
<div className="comet-body-s truncate text-foreground">{name}</div>
7878
{description && (
7979
<TooltipWrapper content={description}>
8080
<div className="comet-body-xs line-clamp-2 text-muted-foreground">
@@ -91,14 +91,14 @@ export const SelectItem: React.FC<SelectItemProps> = ({
9191
return (
9292
<div
9393
className={cn(
94-
"group relative flex min-h-12 cursor-pointer items-start gap-2 rounded-md py-2 pl-6 pr-4 hover:bg-primary-foreground",
94+
"group relative flex min-h-12 cursor-pointer items-start gap-2 rounded-md py-2 pl-8 pr-4 hover:bg-primary-foreground",
9595
isSelected && "bg-primary-foreground",
9696
)}
9797
onClick={() => onSelect(id)}
9898
>
9999
{isSelected && (
100100
<Check
101-
className="absolute left-1 top-2.5 size-3.5 text-foreground"
101+
className="absolute left-2 top-2.5 size-3.5 text-muted-slate"
102102
strokeWidth="3"
103103
/>
104104
)}
@@ -109,7 +109,7 @@ export const SelectItem: React.FC<SelectItemProps> = ({
109109
)}
110110
/>
111111
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
112-
<div className="comet-body-s truncate">{name}</div>
112+
<div className="comet-body-s truncate text-foreground">{name}</div>
113113
{description && (
114114
<TooltipWrapper content={description}>
115115
<div className="comet-body-xs line-clamp-2 text-muted-foreground">
@@ -124,7 +124,7 @@ export const SelectItem: React.FC<SelectItemProps> = ({
124124
<Button
125125
variant="ghost"
126126
size="icon-xs"
127-
className="mt-0.5 opacity-0 group-hover:opacity-100"
127+
className="mt-0.5 text-muted-slate opacity-0 group-hover:opacity-100"
128128
onClick={(e) => e.stopPropagation()}
129129
>
130130
<MoreVertical className="size-4" />
@@ -140,6 +140,7 @@ export const SelectItem: React.FC<SelectItemProps> = ({
140140
e.stopPropagation();
141141
onEdit?.(dashboard);
142142
}}
143+
className="hover:text-primary"
143144
>
144145
<Pencil className="mr-2 size-4" />
145146
Edit
@@ -159,7 +160,7 @@ export const SelectItem: React.FC<SelectItemProps> = ({
159160
e.stopPropagation();
160161
onDelete?.(dashboard);
161162
}}
162-
className="text-destructive focus:text-destructive"
163+
className="text-destructive focus:bg-transparent focus:text-destructive"
163164
>
164165
<Trash2 className="mr-2 size-4" />
165166
Delete

apps/opik-frontend/src/components/pages-shared/experiments/ExperimentsSelectBox/ExperimentsSelectBox.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { DropdownOption } from "@/types/shared";
99
import useAppStore from "@/store/AppStore";
1010
import { Experiment } from "@/types/datasets";
1111
import { Sorting } from "@/types/sorting";
12+
import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper";
1213

1314
const DEFAULT_LOADED_EXPERIMENT_ITEMS = 1000;
1415
const MORE_LOADED_EXPERIMENT_ITEMS = 10000;
@@ -159,6 +160,22 @@ const ExperimentsSelectBox: React.FC<ExperimentsSelectBoxProps> = (props) => {
159160
: experimentOptions;
160161
}, [experiments, customOptions]);
161162

163+
const renderExperimentsTitle = useCallback(
164+
(selectedOptions: DropdownOption<string>[]) => {
165+
const count = selectedOptions.length;
166+
if (count === 1) {
167+
return <div className="truncate">{selectedOptions[0].label}</div>;
168+
}
169+
const experimentNames = selectedOptions.map((o) => o.label).join(", ");
170+
return (
171+
<TooltipWrapper content={experimentNames}>
172+
<div className="truncate">{count} experiments</div>
173+
</TooltipWrapper>
174+
);
175+
},
176+
[],
177+
);
178+
162179
const loadableSelectBoxProps = props.multiselect
163180
? {
164181
options,
@@ -168,6 +185,7 @@ const ExperimentsSelectBox: React.FC<ExperimentsSelectBoxProps> = (props) => {
168185
multiselect: true as const,
169186
showSelectAll: props.showSelectAll,
170187
selectAllLabel: props.selectAllLabel || "All experiments",
188+
renderTitle: renderExperimentsTitle,
171189
}
172190
: {
173191
options,

apps/opik-frontend/src/components/shared/Dashboard/widgets/ExperimentsFeedbackScoresWidget/ExperimentsFeedbackScoresWidget.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ const ExperimentsFeedbackScoresWidget: React.FunctionComponent<
244244

245245
const infoMessage =
246246
isUsingGlobalExperiments && isSelectExperimentsMode
247-
? "Using global experiment configuration"
247+
? "Using the dashboard's default experiment settings"
248248
: undefined;
249249

250250
// Limit to first 10 experiments

apps/opik-frontend/src/components/shared/Dashboard/widgets/ExperimentsFeedbackScoresWidget/ExperimentsFeedbackScoresWidgetEditor.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import FeedbackDefinitionsAndScoresSelectBox, {
2626
ScoreSource,
2727
} from "@/components/pages-shared/experiments/FeedbackDefinitionsAndScoresSelectBox/FeedbackDefinitionsAndScoresSelectBox";
2828
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
29-
import { Filter, ListFilter } from "lucide-react";
29+
import { Filter, ListChecks } from "lucide-react";
3030

3131
import { cn } from "@/lib/utils";
3232
import { Filters } from "@/types/filters";
@@ -283,19 +283,19 @@ const ExperimentsFeedbackScoresWidgetEditor = forwardRef<
283283
>
284284
<ToggleGroupItem
285285
value={EXPERIMENT_DATA_SOURCE.FILTER_AND_GROUP}
286-
aria-label="Filter and group"
286+
aria-label="Filter experiments"
287287
className="gap-1.5"
288288
>
289289
<Filter className="size-3.5" />
290-
<span>Filter and group</span>
290+
<span>Filter experiments</span>
291291
</ToggleGroupItem>
292292
<ToggleGroupItem
293293
value={EXPERIMENT_DATA_SOURCE.SELECT_EXPERIMENTS}
294-
aria-label="Select experiments"
294+
aria-label="Manual selection"
295295
className="gap-1.5"
296296
>
297-
<ListFilter className="size-3.5" />
298-
<span>Select experiments</span>
297+
<ListChecks className="size-3.5" />
298+
<span>Manual selection</span>
299299
</ToggleGroupItem>
300300
</ToggleGroup>
301301
</FormControl>
@@ -328,7 +328,7 @@ const ExperimentsFeedbackScoresWidgetEditor = forwardRef<
328328
]);
329329
return (
330330
<FormItem>
331-
<FormLabel>Select experiments</FormLabel>
331+
<FormLabel>Manual selection</FormLabel>
332332
<FormControl>
333333
<ExperimentsSelectBox
334334
value={field.value || []}
@@ -345,9 +345,9 @@ const ExperimentsFeedbackScoresWidgetEditor = forwardRef<
345345
/>
346346
</FormControl>
347347
<Description>
348-
Select experiments for this widget. If not set, the
349-
dashboard&apos;s global experiment configuration will be
350-
used.
348+
Widgets use the dashboard&apos;s experiment settings by
349+
default. You can override them here and select different
350+
experiments.
351351
</Description>
352352
<FormMessage />
353353
</FormItem>

apps/opik-frontend/src/components/shared/Dashboard/widgets/shared/ExperimentWidgetDataSection/ExperimentWidgetDataSection.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import GroupsAccordionSection, {
2424
} from "@/components/shared/GroupsAccordionSection/GroupsAccordionSection";
2525
import DatasetSelectBox from "@/components/pages-shared/experiments/DatasetSelectBox/DatasetSelectBox";
2626
import ExperimentsPathsAutocomplete from "@/components/pages-shared/experiments/ExperimentsPathsAutocomplete/ExperimentsPathsAutocomplete";
27+
import { Description } from "@/components/ui/description";
2728

2829
type ExperimentColumnData = {
2930
id: string;
@@ -167,14 +168,18 @@ const ExperimentWidgetDataSection = <T extends FieldValues>({
167168
: undefined;
168169

169170
return (
170-
<div className={cn("flex flex-col gap-4", className)}>
171+
<div className={cn("flex flex-col", className)}>
172+
<Description className="mb-2">
173+
Add filters to focus on specific experiments and group them by
174+
configuration to aggregate feedback scores.
175+
</Description>
176+
171177
<FiltersAccordionSection
172178
columns={EXPERIMENT_DATA_COLUMNS as ColumnData<unknown>[]}
173179
config={dataConfig}
174180
filters={filters}
175181
onChange={setFilters}
176182
label="Filters"
177-
description="Add filters to focus the widget on specific experiments."
178183
errors={parsedFilterErrors}
179184
/>
180185

@@ -184,10 +189,10 @@ const ExperimentWidgetDataSection = <T extends FieldValues>({
184189
groups={groups}
185190
onChange={setGroups}
186191
label="Group by"
187-
description="Group experiments by configuration to aggregate feedback scores."
188192
errors={parsedGroupErrors}
189193
className="w-full"
190194
hideSorting
195+
hideBorder
191196
/>
192197
</div>
193198
);

apps/opik-frontend/src/components/shared/FiltersAccordionSection/FiltersAccordionSection.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { useCallback } from "react";
22
import { Plus } from "lucide-react";
33

44
import { Button } from "@/components/ui/button";
5-
import { Label } from "@/components/ui/label";
65
import { Description } from "@/components/ui/description";
76
import { FormErrorSkeleton } from "@/components/ui/form";
87
import { cn } from "@/lib/utils";
@@ -34,6 +33,7 @@ type FiltersAccordionSectionProps<TColumnData> = {
3433
description?: string;
3534
className?: string;
3635
errors?: (FilterValidationError | undefined)[];
36+
hideBorder?: boolean;
3737
};
3838

3939
const FiltersAccordionSection = <TColumnData,>({
@@ -45,6 +45,7 @@ const FiltersAccordionSection = <TColumnData,>({
4545
description = "Add filters",
4646
className = "",
4747
errors,
48+
hideBorder = false,
4849
}: FiltersAccordionSectionProps<TColumnData>) => {
4950
const handleAddFilter = useCallback(() => {
5051
const newFilter: Filter = {
@@ -58,11 +59,9 @@ const FiltersAccordionSection = <TColumnData,>({
5859

5960
return (
6061
<Accordion type="single" collapsible className={cn("w-full", className)}>
61-
<AccordionItem value="filters" className="border-t">
62+
<AccordionItem value="filters" className={hideBorder ? "" : "border-t"}>
6263
<AccordionTrigger className="py-3 hover:no-underline">
63-
<Label className="text-sm font-medium">
64-
{label} {filters.length > 0 && `(${filters.length})`}
65-
</Label>
64+
{label} {filters.length > 0 && `(${filters.length})`}
6665
</AccordionTrigger>
6766
<AccordionContent className="flex flex-col gap-4 px-3 pb-3">
6867
<Description>{description}</Description>

apps/opik-frontend/src/components/shared/GroupsAccordionSection/GroupsAccordionSection.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { useCallback } from "react";
22
import { Plus } from "lucide-react";
33

44
import { Button } from "@/components/ui/button";
5-
import { Label } from "@/components/ui/label";
65
import { Description } from "@/components/ui/description";
76
import { FormErrorSkeleton } from "@/components/ui/form";
87
import {
@@ -36,6 +35,7 @@ type GroupsAccordionSectionProps<TColumnData> = {
3635
className?: string;
3736
errors?: (GroupValidationError | undefined)[];
3837
hideSorting?: boolean;
38+
hideBorder?: boolean;
3939
};
4040

4141
const GroupsAccordionSection = <TColumnData,>({
@@ -48,6 +48,7 @@ const GroupsAccordionSection = <TColumnData,>({
4848
className = "",
4949
errors,
5050
hideSorting = false,
51+
hideBorder = false,
5152
}: GroupsAccordionSectionProps<TColumnData>) => {
5253
const handleAddGroup = useCallback(() => {
5354
if (groups.length >= MAX_GROUP_LEVELS) return;
@@ -65,11 +66,9 @@ const GroupsAccordionSection = <TColumnData,>({
6566

6667
return (
6768
<Accordion type="single" collapsible className={className}>
68-
<AccordionItem value="groups" className="border-t">
69+
<AccordionItem value="groups" className={hideBorder ? "" : "border-t"}>
6970
<AccordionTrigger className="py-3 hover:no-underline">
70-
<Label className="text-sm font-medium">
71-
{label} {groups.length > 0 && `(${groups.length})`}
72-
</Label>
71+
{label} {groups.length > 0 && `(${groups.length})`}
7372
</AccordionTrigger>
7473
<AccordionContent className="flex flex-col gap-4 px-3 pb-3">
7574
<Description>{description}</Description>

apps/opik-frontend/src/lib/dashboard/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ export const UNSET_PROJECT_OPTION = [
3232

3333
export const UNSET_PROJECT_VALUE = "";
3434

35-
export const GLOBAL_PROJECT_CONFIG_MESSAGE = "Using global project config";
35+
export const GLOBAL_PROJECT_CONFIG_MESSAGE =
36+
"Using the dashboard's default project settings";
3637

3738
export const WIDGET_PROJECT_SELECTOR_DESCRIPTION =
38-
"Pick the project for this widget. If not set, the dashboard's global project config will be used.";
39+
"Widgets use the dashboard's project settings by default. You can override them here and select a different project.";
3940

4041
export const resolveProjectIdFromConfig = (
4142
widgetProjectId: string | undefined,

0 commit comments

Comments
 (0)