Skip to content

Commit 10bb6aa

Browse files
Merge branch 'develop' into 'fb-fit-132/date-picker-filter-dropdown-data-manager-not-optimized-dark-mode'
Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/16349287722
2 parents 1ee9d6e + f8b2828 commit 10bb6aa

File tree

7 files changed

+181
-3
lines changed

7 files changed

+181
-3
lines changed

label_studio/io_storages/base_models.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
from webhooks.models import WebhookAction
3636
from webhooks.utils import emit_webhooks_for_instance
3737

38+
from .exceptions import UnsupportedFileFormatError
39+
3840
logger = logging.getLogger(__name__)
3941

4042

@@ -451,7 +453,7 @@ def _scan_and_create_links(self, link_class):
451453
json_extensions = {'.json', '.jsonl', '.parquet'}
452454

453455
if ext and ext not in json_extensions:
454-
raise ValueError(
456+
raise UnsupportedFileFormatError(
455457
f'File "{key}" is not a JSON/JSONL/Parquet file. Only .json, .jsonl, and .parquet files can be processed.\n'
456458
f"If you're trying to import non-JSON data (images, audio, text, etc.), "
457459
f'edit storage settings and enable "Treat every bucket object as a source file"'
@@ -571,7 +573,14 @@ class Meta:
571573
@job('low')
572574
def import_sync_background(storage_class, storage_id, timeout=settings.RQ_LONG_JOB_TIMEOUT, **kwargs):
573575
storage = storage_class.objects.get(id=storage_id)
574-
storage.scan_and_create_links()
576+
try:
577+
storage.scan_and_create_links()
578+
except UnsupportedFileFormatError:
579+
# This is an expected error when user tries to import non-JSON files without enabling blob URLs
580+
# We don't want to fail the job in this case, just mark the storage as failed with a clear message
581+
storage.info_set_failed()
582+
# Exit gracefully without raising exception to avoid job failure
583+
return
575584

576585

577586
@job('low', timeout=settings.RQ_LONG_JOB_TIMEOUT)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class UnsupportedFileFormatError(ValueError):
2+
pass

web/libs/editor/LSF.init.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Different thoughts and investingations related to LSF init.
44

55
## App render
66

7+
TODO: Rewrite this part when both FF_1170 and FF_3873 are gone! First is removed, the second is still there.
8+
79
We have 3 UI versions: old (awful), medium (outliner v1), modern (draggable panels)
810

911
Flag:
@@ -12,7 +14,7 @@ Flag:
1214

1315
Components:
1416

15-
- modern (FF_1170 + FF_3873): `SideTabsPanels` + `SidePanels/TabPanels` + `OutlinerTree` + `BottomBar`
17+
- modern (FF_1170 + FF_3873): `SidePanels/TabPanels/SideTabsPanels` (sic!!) + `OutlinerTree` + `BottomBar`
1618
- medium (FF_1170): `SidePanels` + `OutlinerTree`
1719
- old: `SidebarTabs` + `AnnotationTab` + `Entities/RegionTree` and surprisingly `BottomBar` if FF_3873 enabled without FF_1170
1820

@@ -82,6 +84,8 @@ called from 3 places:
8284

8385
## Huge mess with "side panels"
8486

87+
TODO: Rewrite this part when both FF_1170 and FF_3873 are gone! First is removed, the second is still there.
88+
8589
We have three versions of interface:
8690
1. with DEV-1170 FF off we have old interface with `components/SidebarTabs`
8791
2. with DEV-1170 FF on and DEV-3873 off we have intermediate interface with `components/SidePanels/SidePanels.tsx`

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/* istanbul ignore file */
2+
// @deprecated should be removed along with FF_DEV_3873
3+
14
import { observer } from "mobx-react";
25
import { type CSSProperties, type FC, Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
36
import { Block, Elem } from "../../utils/bem";
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const dataSimple = {
2+
text: "This text exists for testing purposes",
3+
};
4+
5+
export const configSimple = `<View>
6+
<Labels name="lbl" toName="text">
7+
<Label value="Word1" />
8+
<Label value="Word2" />
9+
</Labels>
10+
<Text name="text" value="$text" inline="true"/>
11+
</View>`;
12+
13+
export const resultSimple = {
14+
id: "reg1",
15+
type: "labels",
16+
from_name: "lbl",
17+
to_name: "text",
18+
score: 0.89,
19+
value: {
20+
start: 5,
21+
end: 9,
22+
labels: ["Word1"],
23+
text: "text",
24+
},
25+
};
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Labels, LabelStudio } from "@humansignal/frontend-test/helpers/LSF";
2+
import { RichText } from "@humansignal/frontend-test/helpers/LSF/RichText";
3+
import { FF_DEV_3873 } from "libs/editor/src/utils/feature-flags";
4+
import { configSimple, dataSimple, resultSimple } from "../../data/core/info_panels";
5+
6+
describe("Label Studio UI info panels", () => {
7+
it("Open every panel and interact with regions", () => {
8+
LabelStudio.setFeatureFlagsOnPageLoad({
9+
[FF_DEV_3873]: true,
10+
});
11+
12+
LabelStudio.init({
13+
config: configSimple,
14+
task: {
15+
annotations: [{ id: 1, result: [resultSimple] }],
16+
predictions: [],
17+
id: 1,
18+
data: dataSimple,
19+
},
20+
});
21+
22+
// Info panel is active and it has empty state
23+
cy.get("[class$=tab-container_active]").find("#Info-draggable").should("exist");
24+
cy.contains("View region details").should("be.visible");
25+
26+
// Regions panel is also active but has one region in it
27+
cy.get("[class$=tab-container_active]").find("#Regions-draggable").should("exist");
28+
// This region's score is visible
29+
cy.get("[class$=control_type_score]").should("have.text", "0.89");
30+
31+
// Change regions grouping by openining dropdown; it will be closed automatically
32+
cy.contains("Manual").click();
33+
cy.contains("Group by Label").should("be.visible");
34+
// waiting for dropdown to be fully visible
35+
// @todo better options?
36+
cy.wait(200);
37+
cy.contains("Group by Label").click();
38+
cy.get("[class$=lsf-outliner-item__title").contains("Word1").should("be.visible");
39+
cy.contains("Group by Label").should("not.be.visible");
40+
41+
// Select Relations panel, empty state is shown, Regions will be unselected
42+
cy.get("#Relations-draggable").click();
43+
cy.get("[class$=tab-container_active]").find("#Regions-draggable").should("not.exist");
44+
cy.contains("Create relations between regions").should("be.visible");
45+
46+
// Select History panel, Info panel will be unselected
47+
cy.get("#History-draggable").click();
48+
// No annotation history so far
49+
cy.get("[class$=tab-container_active]").find("#Info-draggable").should("not.exist");
50+
cy.contains("See a log of user actions").should("be.visible");
51+
52+
// Change editor settings to keep region selected
53+
cy.get("[aria-label='Settings']").click();
54+
cy.contains("Select region after creating it").click();
55+
cy.get("[aria-label='Close']").click();
56+
57+
// Create second region in Text tag
58+
Labels.select("Word2");
59+
RichText.selectText("This");
60+
RichText.hasRegionWithText("This");
61+
62+
Labels.selectedLabel.contains("Word2").should("exist");
63+
64+
// Create relation between regions by hotkey
65+
cy.get("body").type("{alt}{r}");
66+
RichText.findRegionWithText("text").click();
67+
// First click will trigger hover, so we need second click to actually click on the region
68+
RichText.findRegionWithText("text").click();
69+
70+
// There is a new relation in a list
71+
cy.contains("Relations (1)").should("be.visible");
72+
cy.get("[data-testid='detailed-region']").contains("Word2").should("be.visible");
73+
cy.get("[data-testid='detailed-region']").contains("Word1").should("be.visible");
74+
75+
// And there is also new regions group in a list
76+
cy.get("#Regions-draggable").click();
77+
cy.get("[class$=lsf-outliner-item__title").contains("Word2").should("be.visible");
78+
79+
// And draft panel just appeared in a history
80+
cy.get("[class$=history-item_selected]").find("[data-reason='Draft']").should("be.visible");
81+
});
82+
});

web/libs/editor/tests/integration/e2e/core/label_studio.cy.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { LabelStudio } from "@humansignal/frontend-test/helpers/LSF";
2+
import { FF_DEV_3873, FF_SIMPLE_INIT } from "libs/editor/src/utils/feature-flags";
23

34
describe("Label Studio UI init", () => {
45
it("Initialize empty Label Studio", () => {
@@ -14,6 +15,7 @@ describe("Label Studio UI init", () => {
1415
},
1516
},
1617
});
18+
cy.contains("No more annotations").should("be.visible");
1719
});
1820

1921
it("Initialize Label Studio", () => {
@@ -29,5 +31,56 @@ describe("Label Studio UI init", () => {
2931
},
3032
},
3133
});
34+
cy.contains("Labeled regions will appear here").should("be.visible");
35+
});
36+
37+
it("Initialize Label Studio with simple init FF (LEAP-443)", () => {
38+
const callApi = cy.spy().as("callApi");
39+
40+
// testing both new UI and simple init FF
41+
LabelStudio.setFeatureFlagsOnPageLoad({
42+
[FF_SIMPLE_INIT]: true,
43+
[FF_DEV_3873]: true,
44+
});
45+
46+
LabelStudio.init({
47+
onSubmitAnnotation: (annotation: object) => {
48+
callApi(annotation);
49+
},
50+
config: "<View></View>",
51+
task: {
52+
annotations: [
53+
{ id: 1, result: [] },
54+
{ id: 2, result: [] },
55+
],
56+
predictions: [],
57+
id: 1,
58+
data: {
59+
image:
60+
"https://htx-pub.s3.us-east-1.amazonaws.com/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg",
61+
},
62+
},
63+
});
64+
cy.contains("Labeled regions will appear here").should("be.visible");
65+
// we already have an annotation, so we should have Update button and no Submit button
66+
cy.contains("Update").should("be.visible");
67+
cy.contains("Submit").should("not.exist");
68+
69+
// now we create a new annotation and submit it
70+
cy.get('[aria-label="Create an annotation"]').click();
71+
cy.contains("Update").should("not.exist");
72+
cy.contains("Submit").should("be.visible");
73+
74+
// submit by hotkey
75+
cy.get("body").type(Cypress.platform === "darwin" ? "{cmd}{enter}" : "{ctrl}{enter}");
76+
77+
// we should have called the API once
78+
cy.get("@callApi").should("have.been.calledOnce");
79+
80+
// @todo technically we should have Update button and no Submit button again,
81+
// @todo but currently this logic is crazy and we reload task in LSO/LSE anyway;
82+
// @todo so at least we check that there is only one of these two buttons.
83+
cy.contains("Submit").should("be.visible");
84+
cy.contains("Update").should("not.exist");
3285
});
3386
});

0 commit comments

Comments
 (0)