From 126d97b22e04d580b81d5359c41dfbed82202ab4 Mon Sep 17 00:00:00 2001 From: LeireA Date: Wed, 9 Mar 2022 16:04:48 +0100 Subject: [PATCH 1/8] fix(#1238): show prediction labels when annotating rule fix #1238 This PR shows prediction labels when annotating rule in Weak Supervision --- .../labeling-rules/RuleLabelsDefinition.vue | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue b/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue index c342c86e8c..0b597401ce 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue @@ -160,8 +160,60 @@ export default { query() { return this.dataset.query.text; }, + annotationLabels() { + const annotationLabels = []; + this.dataset.results.records.map((record) => { + if (record.annotation) { + record.annotation.labels.map((label) => { + annotationLabels.push(label.class); + }); + } + }); + const uniqueLabels = [...new Set(annotationLabels)]; + return uniqueLabels.map((label) => (label = { class: label })); + }, + predictionLabels() { + const predictionLabels = []; + this.dataset.results.records.map((record) => { + if (record.prediction) { + record.prediction.labels.map((label) => { + predictionLabels.push(label.class); + }); + } + }); + const uniqueLabels = [...new Set(predictionLabels)]; + return uniqueLabels.map((label) => (label = { class: label })); + }, labels() { - return this.dataset.labels.map((l) => ({ class: l, selected: false })); + // Setup all record labels + const labels = Object.assign( + {}, + ...this.dataset.labels.map((label) => ({ + [label]: { selected: false }, + })) + ); + // Update info with annotated ones + this.annotationLabels.forEach((label) => { + labels[label.class] = { + class: label.class, + selected: true, + }; + }); + // Update info with predicted ones + this.predictionLabels.forEach((label) => { + const currentLabel = labels[label.class] || label; + labels[label.class] = { + ...currentLabel, + selected: false, + }; + }); + // Dict -> list + return Object.entries(labels).map(([key, value]) => { + return { + class: key, + ...value, + }; + }); }, filteredLabels() { return this.labels.filter((label) => From 417e75184aa51e2e702e7666be5205d3ef4f64b1 Mon Sep 17 00:00:00 2001 From: Francisco Aranda Date: Wed, 9 Mar 2022 23:00:21 +0100 Subject: [PATCH 2/8] fix(metrics): compute dataset labels as python metric --- .../tasks/text_classification/metrics.py | 44 ++++------------ tests/server/metrics/test_api.py | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/rubrix/server/tasks/text_classification/metrics.py b/src/rubrix/server/tasks/text_classification/metrics.py index 663089d0ed..ee41c9f333 100644 --- a/src/rubrix/server/tasks/text_classification/metrics.py +++ b/src/rubrix/server/tasks/text_classification/metrics.py @@ -1,13 +1,12 @@ -from typing import Any, ClassVar, Dict, Iterable, List, Optional +from typing import Any, ClassVar, Dict, Iterable, List from pydantic import Field from sklearn.metrics import precision_recall_fscore_support from sklearn.preprocessing import MultiLabelBinarizer -from rubrix.server.tasks.commons.metrics import CommonTasksMetrics +from rubrix.server.tasks.commons.metrics import CommonTasksMetrics, GenericRecord from rubrix.server.tasks.commons.metrics.model.base import ( BaseMetric, - BaseTaskMetrics, PythonMetric, TermsAggregation, ) @@ -87,39 +86,16 @@ def apply(self, records: Iterable[TextClassificationRecord]) -> Any: } -class DatasetLabels(TermsAggregation): +class DatasetLabels(PythonMetric): id: str = Field("dataset_labels", const=True) name: str = Field("The dataset labels", const=True) - fixed_size: int = Field(1500, const=True) - script: Dict[str, Any] = Field( - { - "lang": "painless", - "source": """ - def all_labels = []; - def predicted = doc.containsKey('prediction.labels.class_label.keyword') - ? doc['prediction.labels.class_label.keyword'] : null; - def annotated = doc.containsKey('annotation.labels.class_label.keyword') - ? doc['annotation.labels.class_label.keyword'] : null; - - if (predicted != null && predicted.size() > 0) { - for (int i = 0; i < predicted.length; i++) { - all_labels.add(predicted[i]) - } - } - - if (annotated != null && annotated.size() > 0) { - for (int i = 0; i < annotated.length; i++) { - all_labels.add(annotated[i]) - } - } - return all_labels; - """, - }, - const=True, - ) - - def aggregation_result(self, aggregation_result: Dict[str, Any]) -> Dict[str, Any]: - return {"labels": [k for k in (aggregation_result or {}).keys()]} + + def apply(self, records: Iterable[GenericRecord]) -> Dict[str, Any]: + ds_labels = set() + for record in records: + ds_labels.update([label for label in record.annotated_as]) + ds_labels.update([label for label in record.predicted_as]) + return {"labels": ds_labels or []} class TextClassificationMetrics(CommonTasksMetrics[TextClassificationRecord]): diff --git a/tests/server/metrics/test_api.py b/tests/server/metrics/test_api.py index 2f3a855423..b91da48c86 100644 --- a/tests/server/metrics/test_api.py +++ b/tests/server/metrics/test_api.py @@ -209,3 +209,55 @@ def test_dataset_metrics(mocked_client): json={}, ) assert response.status_code == 200, f"{metric}: {response.json()}" + + +def test_dataset_labels_for_text_classification(mocked_client): + records = [ + TextClassificationRecord.parse_obj(data) + for data in [ + { + "id": 0, + "inputs": {"text": "Some test data"}, + "prediction": {"agent": "test", "labels": [{"class": "A"}]}, + }, + { + "id": 1, + "inputs": {"text": "Some test data"}, + "annotation": {"agent": "test", "labels": [{"class": "C"}]}, + }, + { + "id": 2, + "inputs": {"text": "Some test data"}, + "prediction": { + "agent": "test", + "labels": [ + {"class": "A", "score": 0.5}, + { + "class": "B", + "score": 0.5, + }, + ], + }, + "annotation": {"agent": "test", "labels": [{"class": "D"}]}, + }, + ] + ] + request = TextClassificationBulkData(records=records) + dataset = "test_dataset_labels_for_text_classification" + + assert mocked_client.delete(f"/api/datasets/{dataset}").status_code == 200 + + assert ( + mocked_client.post( + f"/api/datasets/{dataset}/TextClassification:bulk", + json=request.dict(by_alias=True), + ).status_code + == 200 + ) + + response = mocked_client.post( + f"/api/datasets/TextClassification/{dataset}/metrics/dataset_labels:summary", + json={}, + ) + assert response.status_code == 200 + assert response.json() == {"labels": ["A", "C", "D"]} From 4bdd78a4d664346280556ed2419ff976f67d05f0 Mon Sep 17 00:00:00 2001 From: Francisco Aranda Date: Thu, 10 Mar 2022 09:27:50 +0100 Subject: [PATCH 3/8] test: fix tests --- tests/server/metrics/test_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/server/metrics/test_api.py b/tests/server/metrics/test_api.py index b91da48c86..5c48b6f692 100644 --- a/tests/server/metrics/test_api.py +++ b/tests/server/metrics/test_api.py @@ -260,4 +260,6 @@ def test_dataset_labels_for_text_classification(mocked_client): json={}, ) assert response.status_code == 200 - assert response.json() == {"labels": ["A", "C", "D"]} + response = response.json() + labels = response["labels"] + assert sorted(labels) == ["A", "C", "D"] From 292079c6eac1f5d57a4108ae303d2818654c103d Mon Sep 17 00:00:00 2001 From: Francisco Aranda Date: Thu, 10 Mar 2022 09:58:05 +0100 Subject: [PATCH 4/8] fix: compute dataset label properly --- .../server/tasks/text_classification/metrics.py | 12 +++++++++--- tests/server/metrics/test_api.py | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/rubrix/server/tasks/text_classification/metrics.py b/src/rubrix/server/tasks/text_classification/metrics.py index ee41c9f333..af7f4406fc 100644 --- a/src/rubrix/server/tasks/text_classification/metrics.py +++ b/src/rubrix/server/tasks/text_classification/metrics.py @@ -90,11 +90,17 @@ class DatasetLabels(PythonMetric): id: str = Field("dataset_labels", const=True) name: str = Field("The dataset labels", const=True) - def apply(self, records: Iterable[GenericRecord]) -> Dict[str, Any]: + def apply(self, records: Iterable[TextClassificationRecord]) -> Dict[str, Any]: ds_labels = set() for record in records: - ds_labels.update([label for label in record.annotated_as]) - ds_labels.update([label for label in record.predicted_as]) + if record.annotation: + ds_labels.update( + [label.class_label for label in record.annotation.labels] + ) + if record.prediction: + ds_labels.update( + [label.class_label for label in record.prediction.labels] + ) return {"labels": ds_labels or []} diff --git a/tests/server/metrics/test_api.py b/tests/server/metrics/test_api.py index 5c48b6f692..31de6713e9 100644 --- a/tests/server/metrics/test_api.py +++ b/tests/server/metrics/test_api.py @@ -223,7 +223,7 @@ def test_dataset_labels_for_text_classification(mocked_client): { "id": 1, "inputs": {"text": "Some test data"}, - "annotation": {"agent": "test", "labels": [{"class": "C"}]}, + "annotation": {"agent": "test", "labels": [{"class": "B"}]}, }, { "id": 2, @@ -233,12 +233,12 @@ def test_dataset_labels_for_text_classification(mocked_client): "labels": [ {"class": "A", "score": 0.5}, { - "class": "B", + "class": "D", "score": 0.5, }, ], }, - "annotation": {"agent": "test", "labels": [{"class": "D"}]}, + "annotation": {"agent": "test", "labels": [{"class": "E"}]}, }, ] ] @@ -262,4 +262,4 @@ def test_dataset_labels_for_text_classification(mocked_client): assert response.status_code == 200 response = response.json() labels = response["labels"] - assert sorted(labels) == ["A", "C", "D"] + assert sorted(labels) == ["A", "B", "D", "E"] From 3dc6fbbbe0a386cf796f48870b8142e7bbef82c7 Mon Sep 17 00:00:00 2001 From: LeireA Date: Thu, 10 Mar 2022 09:58:44 +0100 Subject: [PATCH 5/8] fix show all labels for empty query view --- .../labeling-rules/RuleDefinition.vue | 58 ++++++++++++++++++- .../labeling-rules/RuleEmptyQuery.vue | 54 ++++++++++++++++- .../labeling-rules/RuleLabelsDefinition.vue | 58 +------------------ 3 files changed, 113 insertions(+), 57 deletions(-) diff --git a/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue b/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue index 87327443a3..8e135c24b2 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue @@ -2,10 +2,11 @@
- + { + if (record.annotation) { + record.annotation.labels.map((label) => { + annotationLabels.push(label.class); + }); + } + }); + const uniqueLabels = [...new Set(annotationLabels)]; + return uniqueLabels.map((label) => (label = { class: label })); + }, + predictionLabels() { + const predictionLabels = []; + this.dataset.results.records.map((record) => { + if (record.prediction) { + record.prediction.labels.map((label) => { + predictionLabels.push(label.class); + }); + } + }); + const uniqueLabels = [...new Set(predictionLabels)]; + return uniqueLabels.map((label) => (label = { class: label })); + }, + labels() { + // Setup all record labels + const labels = Object.assign( + {}, + ...this.dataset.labels.map((label) => ({ + [label]: { selected: false }, + })) + ); + // Update info with annotated ones + this.annotationLabels.forEach((label) => { + labels[label.class] = { + class: label.class, + selected: true, + }; + }); + // Update info with predicted ones + this.predictionLabels.forEach((label) => { + const currentLabel = labels[label.class] || label; + labels[label.class] = { + ...currentLabel, + selected: false, + }; + }); + // Dict -> list + return Object.entries(labels).map(([key, value]) => { + return { + class: key, + ...value, + }; + }); + }, }, methods: { async updateCurrentRule({ query, label }) { diff --git a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue index 4ba78129a8..fe5e825613 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue @@ -71,8 +71,60 @@ export default { query() { return this.dataset.query.text; }, + annotationLabels() { + const annotationLabels = []; + this.dataset.results.records.map((record) => { + if (record.annotation) { + record.annotation.labels.map((label) => { + annotationLabels.push(label.class); + }); + } + }); + const uniqueLabels = [...new Set(annotationLabels)]; + return uniqueLabels.map((label) => (label = { class: label })); + }, + predictionLabels() { + const predictionLabels = []; + this.dataset.results.records.map((record) => { + if (record.prediction) { + record.prediction.labels.map((label) => { + predictionLabels.push(label.class); + }); + } + }); + const uniqueLabels = [...new Set(predictionLabels)]; + return uniqueLabels.map((label) => (label = { class: label })); + }, labels() { - return this.dataset.labels.map((l) => ({ class: l, selected: false })); + // Setup all record labels + const labels = Object.assign( + {}, + ...this.dataset.labels.map((label) => ({ + [label]: { selected: false }, + })) + ); + // Update info with annotated ones + this.annotationLabels.forEach((label) => { + labels[label.class] = { + class: label.class, + selected: true, + }; + }); + // Update info with predicted ones + this.predictionLabels.forEach((label) => { + const currentLabel = labels[label.class] || label; + labels[label.class] = { + ...currentLabel, + selected: false, + }; + }); + // Dict -> list + return Object.entries(labels).map(([key, value]) => { + return { + class: key, + ...value, + }; + }); }, sortedLabels() { return this.labels.slice().sort((a, b) => (a.score > b.score ? -1 : 1)); diff --git a/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue b/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue index 0b597401ce..ca9c49ce1d 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue @@ -103,6 +103,9 @@ export default { type: TextClassificationDataset, required: true, }, + labels: { + type: Array, + }, isSaved: { type: Boolean, default: false, @@ -160,61 +163,6 @@ export default { query() { return this.dataset.query.text; }, - annotationLabels() { - const annotationLabels = []; - this.dataset.results.records.map((record) => { - if (record.annotation) { - record.annotation.labels.map((label) => { - annotationLabels.push(label.class); - }); - } - }); - const uniqueLabels = [...new Set(annotationLabels)]; - return uniqueLabels.map((label) => (label = { class: label })); - }, - predictionLabels() { - const predictionLabels = []; - this.dataset.results.records.map((record) => { - if (record.prediction) { - record.prediction.labels.map((label) => { - predictionLabels.push(label.class); - }); - } - }); - const uniqueLabels = [...new Set(predictionLabels)]; - return uniqueLabels.map((label) => (label = { class: label })); - }, - labels() { - // Setup all record labels - const labels = Object.assign( - {}, - ...this.dataset.labels.map((label) => ({ - [label]: { selected: false }, - })) - ); - // Update info with annotated ones - this.annotationLabels.forEach((label) => { - labels[label.class] = { - class: label.class, - selected: true, - }; - }); - // Update info with predicted ones - this.predictionLabels.forEach((label) => { - const currentLabel = labels[label.class] || label; - labels[label.class] = { - ...currentLabel, - selected: false, - }; - }); - // Dict -> list - return Object.entries(labels).map(([key, value]) => { - return { - class: key, - ...value, - }; - }); - }, filteredLabels() { return this.labels.filter((label) => label.class.toLowerCase().match(this.searchText.toLowerCase()) From 010076b58a5e8539796f0854adb059332c66d4ce Mon Sep 17 00:00:00 2001 From: LeireA Date: Thu, 10 Mar 2022 10:05:07 +0100 Subject: [PATCH 6/8] empty query --- .../labeling-rules/RuleEmptyQuery.vue | 58 +------------------ 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue index fe5e825613..2b67dd2255 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue @@ -58,6 +58,9 @@ export default { type: Object, required: true, }, + labels: { + type: Array, + } }, data: () => { return { @@ -71,61 +74,6 @@ export default { query() { return this.dataset.query.text; }, - annotationLabels() { - const annotationLabels = []; - this.dataset.results.records.map((record) => { - if (record.annotation) { - record.annotation.labels.map((label) => { - annotationLabels.push(label.class); - }); - } - }); - const uniqueLabels = [...new Set(annotationLabels)]; - return uniqueLabels.map((label) => (label = { class: label })); - }, - predictionLabels() { - const predictionLabels = []; - this.dataset.results.records.map((record) => { - if (record.prediction) { - record.prediction.labels.map((label) => { - predictionLabels.push(label.class); - }); - } - }); - const uniqueLabels = [...new Set(predictionLabels)]; - return uniqueLabels.map((label) => (label = { class: label })); - }, - labels() { - // Setup all record labels - const labels = Object.assign( - {}, - ...this.dataset.labels.map((label) => ({ - [label]: { selected: false }, - })) - ); - // Update info with annotated ones - this.annotationLabels.forEach((label) => { - labels[label.class] = { - class: label.class, - selected: true, - }; - }); - // Update info with predicted ones - this.predictionLabels.forEach((label) => { - const currentLabel = labels[label.class] || label; - labels[label.class] = { - ...currentLabel, - selected: false, - }; - }); - // Dict -> list - return Object.entries(labels).map(([key, value]) => { - return { - class: key, - ...value, - }; - }); - }, sortedLabels() { return this.labels.slice().sort((a, b) => (a.score > b.score ? -1 : 1)); }, From 2e8518af65392465a5735238913a5077405c51ad Mon Sep 17 00:00:00 2001 From: Francisco Aranda Date: Thu, 10 Mar 2022 11:59:32 +0100 Subject: [PATCH 7/8] refactor: revert comp. changes and compute labels in model --- .../labeling-rules/RuleDefinition.vue | 58 +------------------ .../labeling-rules/RuleEmptyQuery.vue | 6 +- .../labeling-rules/RuleLabelsDefinition.vue | 6 +- frontend/models/TextClassification.js | 12 ++++ 4 files changed, 19 insertions(+), 63 deletions(-) diff --git a/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue b/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue index 8e135c24b2..87327443a3 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleDefinition.vue @@ -2,11 +2,10 @@
- + { - if (record.annotation) { - record.annotation.labels.map((label) => { - annotationLabels.push(label.class); - }); - } - }); - const uniqueLabels = [...new Set(annotationLabels)]; - return uniqueLabels.map((label) => (label = { class: label })); - }, - predictionLabels() { - const predictionLabels = []; - this.dataset.results.records.map((record) => { - if (record.prediction) { - record.prediction.labels.map((label) => { - predictionLabels.push(label.class); - }); - } - }); - const uniqueLabels = [...new Set(predictionLabels)]; - return uniqueLabels.map((label) => (label = { class: label })); - }, - labels() { - // Setup all record labels - const labels = Object.assign( - {}, - ...this.dataset.labels.map((label) => ({ - [label]: { selected: false }, - })) - ); - // Update info with annotated ones - this.annotationLabels.forEach((label) => { - labels[label.class] = { - class: label.class, - selected: true, - }; - }); - // Update info with predicted ones - this.predictionLabels.forEach((label) => { - const currentLabel = labels[label.class] || label; - labels[label.class] = { - ...currentLabel, - selected: false, - }; - }); - // Dict -> list - return Object.entries(labels).map(([key, value]) => { - return { - class: key, - ...value, - }; - }); - }, }, methods: { async updateCurrentRule({ query, label }) { diff --git a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue index 2b67dd2255..c5651b3c78 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue @@ -57,9 +57,6 @@ export default { dataset: { type: Object, required: true, - }, - labels: { - type: Array, } }, data: () => { @@ -71,6 +68,9 @@ export default { maxVisibleLabels() { return DatasetViewSettings.MAX_VISIBLE_LABELS; }, + labels() { + return this.dataset.labels.map((l) => ({ class: l, selected: false }));; + }, query() { return this.dataset.query.text; }, diff --git a/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue b/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue index ca9c49ce1d..c342c86e8c 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleLabelsDefinition.vue @@ -103,9 +103,6 @@ export default { type: TextClassificationDataset, required: true, }, - labels: { - type: Array, - }, isSaved: { type: Boolean, default: false, @@ -163,6 +160,9 @@ export default { query() { return this.dataset.query.text; }, + labels() { + return this.dataset.labels.map((l) => ({ class: l, selected: false })); + }, filteredLabels() { return this.labels.filter((label) => label.class.toLowerCase().match(this.searchText.toLowerCase()) diff --git a/frontend/models/TextClassification.js b/frontend/models/TextClassification.js index aa6570a4df..fb1edb58e9 100644 --- a/frontend/models/TextClassification.js +++ b/frontend/models/TextClassification.js @@ -272,16 +272,28 @@ class TextClassificationDataset extends ObservationDataset { get labels() { const { labels } = (this.metadata || {})[USER_DATA_METADATA_KEY] || {}; const aggregations = this.globalResults.aggregations; + const label2str = (label) => label.class; + const recordsLabels = this.results.records.flatMap((record) => { + return [] + .concat( + record.annotation ? record.annotation.labels.map(label2str) : [] + ) + .concat( + record.prediction ? record.prediction.labels.map(label2str) : [] + ); + }); const uniqueLabels = [ ...new Set( (labels || []) .filter((l) => l && l.trim()) .concat(this._labels || []) + .concat(recordsLabels) .concat(Object.keys(aggregations.annotated_as)) .concat(Object.keys(aggregations.predicted_as)) ), ]; + uniqueLabels.sort(); return uniqueLabels; } From 8c4057c377cccadab5b9b58c7544ac6f5afe7555 Mon Sep 17 00:00:00 2001 From: Francisco Aranda Date: Thu, 10 Mar 2022 12:57:15 +0100 Subject: [PATCH 8/8] chore: lint fix --- .../text-classifier/labeling-rules/RuleEmptyQuery.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue index c5651b3c78..08ac5a15cf 100644 --- a/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue +++ b/frontend/components/text-classifier/labeling-rules/RuleEmptyQuery.vue @@ -57,7 +57,7 @@ export default { dataset: { type: Object, required: true, - } + }, }, data: () => { return { @@ -69,7 +69,7 @@ export default { return DatasetViewSettings.MAX_VISIBLE_LABELS; }, labels() { - return this.dataset.labels.map((l) => ({ class: l, selected: false }));; + return this.dataset.labels.map((l) => ({ class: l, selected: false })); }, query() { return this.dataset.query.text;