Skip to content

Commit 2cf91bc

Browse files
Merge branch 'develop' into fb-bros-87/bitmask
2 parents 588606a + f50798c commit 2cf91bc

File tree

7 files changed

+102
-30
lines changed

7 files changed

+102
-30
lines changed

label_studio/projects/migrations/0028_auto_20241107_1031.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def forward_migration(migration_name):
5454
logger.info(f'Deleted {total_deleted} duplicate ProjectMember entries for project ID {project_id}.')
5555

5656
except Exception as e:
57-
migration.status = AsyncMigrationStatus.STATUS_FAILED
57+
migration.status = AsyncMigrationStatus.STATUS_ERROR
5858
migration.save()
5959
logger.error(f'Async migration {migration_name} failed: {e}')
6060
raise

web/libs/datamanager/src/stores/DataStores/tasks.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,14 @@ export const create = (columns) => {
145145

146146
self.setLoading(taskID);
147147

148-
const taskData = yield self.root.apiCall("task", { taskID });
148+
// Pass label stream mode context to the backend API call
149+
const isLabelStream = getRoot(self).SDK?.mode === "labelstream";
150+
const taskParams = { taskID };
151+
if (isLabelStream) {
152+
taskParams.interaction = "labelstream";
153+
}
154+
155+
const taskData = yield self.root.apiCall("task", taskParams);
149156

150157
if (taskData.status === 404) {
151158
self.finishLoading(taskID);

web/libs/editor/src/components/TaskSummary/DataSummary.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,22 @@ export const DataSummary = ({ data_types }: { data_types: ObjectTypes }) => {
2424
cell: ({ getValue }) => {
2525
const value = getValue();
2626

27-
// super simple support for images
27+
// super simple support for images, audio, and video
2828
// @todo create a proper data type handler for all data types
2929
if (type === "image") {
3030
return <img src={value} alt={field} className="w-full" />;
3131
}
3232

33+
if (type === "audio") {
34+
// biome-ignore lint/a11y/useMediaCaption: that's user's media, captions can't be used
35+
return <audio src={value} controls className="w-full" />;
36+
}
37+
38+
if (type === "video") {
39+
// biome-ignore lint/a11y/useMediaCaption: that's user's media, captions can't be used
40+
return <video src={value} controls className="w-full" />;
41+
}
42+
3343
// List: [{ id: <id>, body: text, title: text }, ...]
3444
// Paragraphs: [{ <nameKey>: name, <textKey>: text }, ...]
3545
if (Array.isArray(value)) {

web/libs/editor/src/components/TaskSummary/LabelingSummary.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useMemo } from "react";
22
import { cnm, IconSparks, Userpic } from "@humansignal/ui";
33
import { flexRender, getCoreRowModel, useReactTable, createColumnHelper } from "@tanstack/react-table";
44
import type { ColumnDef, Row } from "@tanstack/react-table";
5-
import type { MSTAnnotation, MSTResult } from "../../stores/types";
5+
import type { MSTAnnotation, MSTResult, RawResult } from "../../stores/types";
66
import { renderers } from "./labelings";
77
import { ResizeHandler } from "./ResizeHandler";
88
import { SummaryBadge } from "./SummaryBadge";
@@ -24,7 +24,7 @@ const cellFn = (control: ControlTag, render: RendererType) => (props: { row: Row
2424
};
2525

2626
const convertPredictionResult = (result: MSTResult) => {
27-
const json = result.toJSON();
27+
const json = result.toJSON() as RawResult;
2828
return {
2929
...json,
3030
// those are real results, so they have full names with @annotation-id postfix
@@ -46,7 +46,7 @@ export const LabelingSummary = ({ annotations: all, controls, onSelect }: Props)
4646
: (annotation.versions.result ?? []),
4747
}));
4848
const columns = useMemo(() => {
49-
const columns: ColumnDef<AnnotationSummary, any>[] = controls.map((control) =>
49+
const columns: ColumnDef<AnnotationSummary, unknown>[] = controls.map((control) =>
5050
columnHelper.display({
5151
id: control.name,
5252
header: () => (
@@ -71,7 +71,11 @@ export const LabelingSummary = ({ annotations: all, controls, onSelect }: Props)
7171
const annotation = row.original;
7272

7373
return (
74-
<div className="flex gap-tight items-center cursor-pointer" onClick={() => onSelect(annotation)}>
74+
<button
75+
type="button"
76+
className="flex gap-tight items-center cursor-pointer"
77+
onClick={() => onSelect(annotation)}
78+
>
7579
<Userpic
7680
user={annotation.user}
7781
className={annotation.type === "prediction" ? "!bg-accent-plum-subtle text-accent-plum-bold" : ""}
@@ -80,7 +84,7 @@ export const LabelingSummary = ({ annotations: all, controls, onSelect }: Props)
8084
</Userpic>
8185
<span>{annotation.user?.displayName ?? annotation.createdBy}</span>
8286
<span>#{annotation.id}</span>
83-
</div>
87+
</button>
8488
);
8589
},
8690
});

web/libs/editor/src/components/TaskSummary/TaskSummary.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ const TaskSummary = ({ annotations: all, store: annotationStore }: TaskSummaryPr
2929
}
3030
};
3131

32-
// Check if agreement should be shown based on project settings
33-
const showAgreement = annotationStore.store.project?.review_settings?.show_agreement_to_reviewers ?? true;
34-
3532
const controlTags: [string, MSTControlTag][] = allTags.filter(([_, control]) => control.isControlTag) as [
3633
string,
3734
MSTControlTag,
@@ -46,7 +43,7 @@ const TaskSummary = ({ annotations: all, store: annotationStore }: TaskSummaryPr
4643
// place all controls with the same to_name together
4744
const grouped = Object.groupBy(controlsList, (control) => control.to_name);
4845
// show global classifications first, then labels, then per-regions
49-
const controls = Object.entries(grouped).flatMap(([_, controls]) => sortControls(controls!));
46+
const controls = Object.entries(grouped).flatMap(([_, controls]) => sortControls(controls ?? []));
5047

5148
const objectTags: ObjectTagEntry[] = allTags.filter(
5249
([_, tag]) => tag.isObjectTag && tag.value.includes("$"),
@@ -62,15 +59,15 @@ const TaskSummary = ({ annotations: all, store: annotationStore }: TaskSummaryPr
6259
{
6360
type: object.type,
6461
value:
65-
"parsedValue" in object
66-
? object.parsedValue
67-
: (object.dataObj ?? object._url ?? object._value ?? object.value),
62+
// @ts-expect-error parsedValue, dataObj and _url are very specific and not added to types
63+
object.parsedValue ?? object.dataObj ?? object._url ?? object._value ?? object.value,
6864
},
6965
]),
7066
);
7167

7268
const values = [
73-
...(showAgreement && typeof task?.agreement === "number"
69+
// if agreement is unavailable for current user it's undefined
70+
...(typeof task?.agreement === "number"
7471
? [
7572
{
7673
title: "Agreement",

web/libs/editor/src/components/TaskSummary/labelings.tsx

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ const resultValue = (result: RawResult) => {
1111
return result.value[result.type];
1212
};
1313

14+
const LabelingChip = ({ children }: { children: string | number }) => {
15+
return (
16+
<span
17+
className={cnm(
18+
"inline-block whitespace-nowrap rounded-4 px-2",
19+
"bg-primary-background border border-primary-emphasis text-accent-grape-dark",
20+
)}
21+
>
22+
{children}
23+
</span>
24+
);
25+
};
26+
1427
const LabelsRenderer: RendererType = (results, control) => {
1528
const labels = results.flatMap(resultValue).flat();
1629

@@ -25,6 +38,7 @@ const LabelsRenderer: RendererType = (results, control) => {
2538
.map(([label, data]) => {
2639
return (
2740
<span
41+
key={label}
2842
className="inline-block px-2 whitespace-nowrap rounded-4"
2943
style={{
3044
borderLeft: `4px solid ${data.border}`,
@@ -52,29 +66,43 @@ export const renderers: Record<string, RendererType> = {
5266
paragraphlabels: LabelsRenderer,
5367
timelinelabels: LabelsRenderer,
5468
bitmasklabels: LabelsRenderer,
55-
number: (results) => {
69+
datetime: (results, control) => {
70+
if (!results.length) return "-";
71+
if (control.per_region) return null;
72+
73+
return resultValue(results[0]);
74+
},
75+
number: (results, control) => {
5676
if (!results.length) return "-";
77+
if (control.per_region) return null;
5778

5879
return resultValue(results[0]);
5980
},
6081
choices: (results) => {
6182
const choices = results.flatMap(resultValue).flat();
62-
const unique = [...new Set(choices)];
83+
const unique: string[] = [...new Set(choices)];
6384

6485
if (!choices.length) return null;
6586

6687
return (
6788
<span className="flex gap-2 flex-wrap">
6889
{unique.map((choice) => (
69-
<span
70-
key={choice}
71-
className={cnm(
72-
"inline-block whitespace-nowrap rounded-4 px-2",
73-
"bg-primary-background border border-primary-emphasis text-accent-grape-dark",
74-
)}
75-
>
76-
{choice}
77-
</span>
90+
<LabelingChip key={choice}>{choice}</LabelingChip>
91+
))}
92+
</span>
93+
);
94+
},
95+
taxonomy: (results, control) => {
96+
if (!results.length) return "-";
97+
if (control.per_region) return null;
98+
99+
// @todo use `pathseparator` from control
100+
const values: string[] = resultValue(results[0]).map((item: string[]) => item.join(" / "));
101+
102+
return (
103+
<span className="flex gap-2 flex-wrap">
104+
{values.map((value) => (
105+
<LabelingChip key={value}>{value}</LabelingChip>
78106
))}
79107
</span>
80108
);
@@ -83,11 +111,36 @@ export const renderers: Record<string, RendererType> = {
83111
if (!results.length) return "-";
84112
if (control.per_region) return null;
85113

86-
const value = resultValue(results[0]);
114+
const texts: string[] = resultValue(results[0]);
87115

88-
if (!value) return null;
116+
if (!texts) return null;
89117

90-
return <span className="text-ellipsis line-clamp-6">{value}</span>;
118+
// biome-ignore lint/suspicious/noArrayIndexKey: this piece won't be rerendered with updated data anyway and texts can be huge
119+
return (
120+
<div className="text-ellipsis line-clamp-6">
121+
{texts.map((text, i) => (
122+
<p key={i}>{text}</p>
123+
))}
124+
</div>
125+
);
126+
},
127+
ranker: (results) => {
128+
if (!results.length) return "-";
129+
130+
const value: Record<string, number[]> = resultValue(results[0]);
131+
132+
return Object.entries(value).map(([bucket, items]) => {
133+
return (
134+
<p key={bucket}>
135+
<b>{bucket}</b>:{" "}
136+
<span className="inline-flex gap-2 flex-wrap">
137+
{items.map((item) => (
138+
<LabelingChip key={item}>{item}</LabelingChip>
139+
))}
140+
</span>
141+
</p>
142+
);
143+
});
91144
},
92145
rating: (results, control) => {
93146
if (!results.length) return "-";

web/libs/editor/src/stores/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type RawResult = {
66
};
77

88
type MSTResult = {
9+
toJSON(): unknown;
910
id: string;
1011
area: MSTRegion;
1112
annotation: MSTAnnotation;

0 commit comments

Comments
 (0)