diff --git a/e2e/tests/taxonomy.test.js b/e2e/tests/taxonomy.test.js new file mode 100644 index 000000000..dabd1553d --- /dev/null +++ b/e2e/tests/taxonomy.test.js @@ -0,0 +1,293 @@ +/* global Feature, Scenario, locate, DataTable, Data */ + +const { serialize, selectText } = require("./helpers"); + +const assert = require("assert"); + +Feature("Taxonomy"); + +const cases = { + taxonomy: { + config: ` + + + + + + + + + + + + + `, + text: `To have faith is to trust yourself to the water`, + annotations: [ + { label: 'PER', rangeStart: 0, rangeEnd: 2, text: 'To', test: { + clickTaxonomy: ['Extraterrestial', 'Archaea'], + assertTrue: [ + 'Archaea', + ], + assertFalse: [ + 'Extraterrestial', + ], + } }, + { label: 'PER', rangeStart: 3, rangeEnd: 7, text: 'have', test: { + clickTaxonomy: ['Archaea'], + assertTrue: [ + 'Archaea', + 'Extraterrestial', + ], + assertFalse: [], + } }, + ], + isPerRegion: false, + FF: { + ff_front_1170_outliner_030222_short: false, + }, + }, + taxonomyPerRegion: { + config: ` + + + + + + + + + + + + + `, + text: `To have faith is to trust yourself to the water`, + annotations: [ + { label: 'PER', rangeStart: 0, rangeEnd: 2, text: 'To', test: { + clickTaxonomy: ['Extraterrestial', 'Archaea'], + assertTrue: [ + 'Archaea', + ], + assertFalse: [ + 'Extraterrestial', + ], + } }, + { label: 'PER', rangeStart: 3, rangeEnd: 7, text: 'have', test: { + clickTaxonomy: ['Archaea'], + assertTrue: [ + 'Archaea', + 'Extraterrestial', + ], + assertFalse: [], + } }, + ], + isPerRegion: true, + FF: { + ff_front_1170_outliner_030222_short: false, + }, + }, + taxonomyWithShowLabels: { + config: ` + + + + + + + + + + + + + `, + text: `To have faith is to trust yourself to the water`, + annotations: [ + { label: 'PER', rangeStart: 0, rangeEnd: 2, text: 'To', test: { + clickTaxonomy: ['Human'], + assertTrue: [ + 'Eukarya / Extraterrestial', + 'Eukarya / Human', + ], + assertFalse: [ + 'Bacteria', + ], + } }, + ], + isPerRegion: true, + FF: { + ff_front_1170_outliner_030222_short: false, + }, + }, + taxonomyOutliner: { + config: ` + + + + + + + + + + + + + `, + text: `To have faith is to trust yourself to the water`, + annotations: [ + { label: 'PER', rangeStart: 0, rangeEnd: 2, text: 'To', test: { + clickTaxonomy: ['Extraterrestial'], + assertTrue: [ + ], + assertFalse: [ + 'Extraterrestial', + ], + } }, + { label: 'PER', rangeStart: 3, rangeEnd: 7, text: 'have', test: { + clickTaxonomy: ['Archaea'], + assertTrue: [ + 'Archaea', + 'Extraterrestial', + ], + assertFalse: [], + } }, + ], + isPerRegion: true, + FF: { + ff_front_1170_outliner_030222_short: true, + }, + }, + taxonomyWithShowLabelsWithOuliner: { + config: ` + + + + + + + + + + + + + `, + text: `To have faith is to trust yourself to the water`, + annotations: [ + { label: 'PER', rangeStart: 0, rangeEnd: 2, text: 'To', test: { + clickTaxonomy: [['Human']], + assertTrue: [ + 'Eukarya / Extraterrestial', + 'Eukarya / Human', + ], + assertFalse: [ + 'Bacteria', + ], + } }, + ], + isPerRegion: true, + FF: { + ff_front_1170_outliner_030222_short: true, + }, + }, +}; + +const taxonomyTable = new DataTable(["taxonomyName"]); + +for (const taxonomyName of Object.keys(cases)) { + taxonomyTable.add([taxonomyName]); +} + +Data(taxonomyTable).Scenario("Check Taxonomy", async ({ I, LabelStudio, current }) => { + const { taxonomyName } = current; + const Taxonomy = cases[taxonomyName]; + const { annotations, config, text, isPerRegion, FF } = Taxonomy; + const outlinerSelector = ".lsf-outliner-item__title"; + const sideBarRegionSelector = "li"; + const taxonomyLabelSelector = ".lsf-taxonomy__label"; + + I.amOnPage("/"); + + LabelStudio.setFeatureFlags({ ff_dev_2007_rework_choices_280322_short: true, ...FF }); + LabelStudio.init({ config, data: { text } }); + + const isOutliner = FF.ff_front_1170_outliner_030222_short; + + annotations.forEach(annotation => { + let regionEl; + + if (isPerRegion) { + I.click(locate(".lsf-label__text").withText(annotation.label)); + I.executeScript(selectText, { + selector: ".lsf-htx-richtext", + rangeStart: annotation.rangeStart, + rangeEnd: annotation.rangeEnd, + }); + + regionEl = isOutliner ? locate(outlinerSelector).withText(annotation.label) : locate(sideBarRegionSelector).withText(annotation.text); + + I.seeElement(regionEl); + I.click(regionEl); + } + + I.click(locate("span").withText("Click to add...")); + I.click(locate(".collapser.collapsed")); + annotation.test.clickTaxonomy.forEach(t => I.click(locate("label").withText(t))); + + + /* reseting clicks */ + I.click(locate(".collapser.open")); + if (isPerRegion) { + I.click(regionEl); + I.click(locate(".lsf-label__text").withText(annotation.label)); + } else { + I.click(locate("span").withText("Click to add...")); + } + }); + + const results = await I.executeScript(serialize); + + results.filter(result => result.value.labels).forEach((result, index) => { + const annotation = annotations[index]; + const expected = { + end: annotation.rangeEnd, + labels: [annotation.label], + start: annotation.rangeStart, + text: annotation.text, + }; + + assert.deepEqual(result.value, expected); + + if (isPerRegion) { + const regionEl = isOutliner ? locate(outlinerSelector).withText(annotation.label) : locate(sideBarRegionSelector).withText(annotation.text); + + I.click(regionEl); + } + + annotation.test.assertTrue.forEach(label => I.seeElement(locate(taxonomyLabelSelector).withText(label))); + annotation.test.assertFalse.forEach(label => I.dontSeeElement(locate(taxonomyLabelSelector).withText(label))); + }); + +}); \ No newline at end of file diff --git a/src/components/Taxonomy/Taxonomy.module.scss b/src/components/Taxonomy/Taxonomy.module.scss index b769dde6d..30d7b92ff 100644 --- a/src/components/Taxonomy/Taxonomy.module.scss +++ b/src/components/Taxonomy/Taxonomy.module.scss @@ -15,6 +15,38 @@ white-space: nowrap; cursor: pointer; } + &__selected { + display: flex; + flex-wrap: wrap; + min-height: 28px; // 24px button + 4px margin + + div { + margin: 0 2px 4px 0; + background: hsl(0, 0%, 95%); + padding: 0; // all the right space should be a clickable button, so no padding + padding-left: 8px; + border-radius: 4px; + display: flex; + align-items: center; + } + + input[type="button"] { + border: none; + background: none; + cursor: pointer; + width: 24px; + height: 24px; + padding: 0; + line-height: 1; + font-weight: 500; + font-size: 20px; + color: #09f; + + &:hover { + color: red; + } + } + } } .taxonomy > span > svg { @@ -24,39 +56,6 @@ transform: none; } -.taxonomy__selected { - display: flex; - flex-wrap: wrap; - min-height: 28px; // 24px button + 4px margin - - div { - margin: 0 2px 4px 0; - background: hsl(0, 0%, 95%); - padding: 0; // all the right space should be a clickable button, so no padding - padding-left: 8px; - border-radius: 4px; - display: flex; - align-items: center; - } - - input[type="button"] { - border: none; - background: none; - cursor: pointer; - width: 24px; - height: 24px; - padding: 0; - line-height: 1; - font-weight: 500; - font-size: 20px; - color: #09f; - - &:hover { - color: red; - } - } -} - .taxonomy__dropdown { position: absolute; z-index: 10; diff --git a/src/components/Taxonomy/Taxonomy.tsx b/src/components/Taxonomy/Taxonomy.tsx index 15186bafe..5288a649f 100644 --- a/src/components/Taxonomy/Taxonomy.tsx +++ b/src/components/Taxonomy/Taxonomy.tsx @@ -5,6 +5,7 @@ import { useToggle } from "../../hooks/useToggle"; import { isArraysEqual } from "../../utils/utilities"; import { LsChevron } from "../../assets/icons"; import TreeStructure from "../TreeStructure/TreeStructure"; +import { Elem } from "../../utils/bem"; import styles from "./Taxonomy.module.scss"; @@ -129,12 +130,12 @@ const SelectedList = ({ isReadonly, flatItems } : { isReadonly:boolean, flatItem return (
{selectedLabels.map((path, index) => ( -
+ {showFullPath ? path.join(pathSeparator) : path[path.length - 1]} {!isReadonly && setSelected(selected[index], false)} value="×" /> } -
+ ))}
); @@ -217,7 +218,11 @@ const Item: React.FC = ({ style, item, dimensionCallback, maxWidth }:
toggle(id)}> - +
{ - if (isFF(FF_DEV_2100_A) && tag?.type === "choices" && tag.preselectedValues?.length) { + let couldHavePreselectedValues = false; + + if (isFF(FF_DEV_2100_A) && tag?.type === "choices") { // - self.createResult({}, { choices: tag.preselectedValues }, tag, tag.toname); + couldHavePreselectedValues = true; + + } else if (tag?.type === "taxonomy") { + couldHavePreselectedValues = true; + } + + if (couldHavePreselectedValues && tag.preselectedValues?.length) { + console.log("setDefaultValues", tag.valueType, tag.preselectedValues, self.selected, self.preselectedValues); + self.createResult({}, { [tag.valueType]: tag.preselectedValues }, tag, tag.toname); } }); }, @@ -765,6 +775,14 @@ export const Annotation = types }; const area = self.areas.put(areaRaw); + const childrenWithPreselectedValues = self.toNames.get(object?.name ?? object)?.toJSON?.()?.filter(item => (item.preselectedValues?.length ?? 0) > 0); + const preRegionChildWithPreselectedValues = childrenWithPreselectedValues.filter(item => item.perregion); + const annotationChildWithPreselectedValues = childrenWithPreselectedValues.filter(item => !item.perregion); + + preRegionChildWithPreselectedValues?.forEach(item => area.setDefaultValue(item)); + annotationChildWithPreselectedValues?.forEach(item => item.selected = item.preselectedValues); + + console.log("annotationChildWithPreselectedValues", annotationChildWithPreselectedValues, self); if (!area.classification) getEnv(self).events.invoke('entityCreate', area); if (!skipAfrerCreate) self.afterCreateResult(area, control); @@ -1063,6 +1081,7 @@ export const Annotation = types }, prepareValue(value, type) { + console.log("prepareValue", value, type); switch (type) { case "text": case "hypertext": diff --git a/src/tags/control/Taxonomy.js b/src/tags/control/Taxonomy.js index cbb31fc75..615d7e20f 100644 --- a/src/tags/control/Taxonomy.js +++ b/src/tags/control/Taxonomy.js @@ -14,6 +14,8 @@ import VisibilityMixin from "../../mixins/Visibility"; import ControlBase from "./Base"; import DynamicChildrenMixin from "../../mixins/DynamicChildrenMixin"; import { FF_DEV_2007_DEV_2008, isFF } from "../../utils/feature-flags"; +import Tree from "../../core/Tree"; +import { Block } from "../../utils/bem"; /** * Use the Taxonomy tag to create one or more hierarchical classifications, storing both choice selections and their ancestors in the results. Use for nested classification tasks with the Choice tag. @@ -160,6 +162,13 @@ const Model = types return fromConfig; }, + get preselectedValues() { + const items = Tree.filterChildrenOfType(self, ['ChoiceModel']); + const selectedItems = items.filter(c => c.selected); + + return selectedItems.map(c => c.resultValue); + }, + get defaultChildType() { return "choice"; }, @@ -224,7 +233,7 @@ const TaxonomyModel = types.compose("TaxonomyModel", const HtxTaxonomy = observer(({ item }) => { const style = { marginTop: "1em", marginBottom: "1em" }; - const visibleStyle = item.perRegionVisible() || item.isVisible ? {} : { display: "none" }; + const visibleStyle = item.perRegionVisible() && item.isVisible ? {} : { display: "none" }; const options = { showFullPath: item.showfullpath, leafsOnly: item.leafsonly, @@ -236,7 +245,7 @@ const HtxTaxonomy = observer(({ item }) => { }; return ( -
+ { options={options} isReadonly={item.annotation.readonly} /> -
+ ); });