From cb72fd9ffca6da4036bd0402de6112e1ba9404e3 Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:53:19 +0100 Subject: [PATCH 01/86] first draft draft of an OpenAPI spec --- .../com/bakdata/conquery/apiv1/IdLabel.java | 2 + openapi.yaml | 177 ++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 openapi.yaml diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/IdLabel.java b/backend/src/main/java/com/bakdata/conquery/apiv1/IdLabel.java index 2dc0857dc0..39fe58b4ba 100644 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/IdLabel.java +++ b/backend/src/main/java/com/bakdata/conquery/apiv1/IdLabel.java @@ -6,6 +6,7 @@ import com.bakdata.conquery.models.identifiable.ids.Id; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; /** * Container class for the frontend to provide a tuple of id and a corresponding label. @@ -14,6 +15,7 @@ */ @Getter @RequiredArgsConstructor +@ToString public class IdLabel> implements Comparable> { @NotEmpty private final I id; diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000000..d5b0415059 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,177 @@ +openapi: 3.1.3 +info: + version: 1.0.0 + title: Conquery API + description: Conquery API + license: + name: TODO + url: http://TODO/ +securityDefinitions: + Bearer: + type: apiKey + name: Authorization + in: header +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + Ingef: + type: http + scheme: bearer + + responses: + unauthorized: + content: + application/json: + schema: + $ref: "#/components/schemas/error" + description: The user is not logged in. + schemas: + mail: + type: string + format: mail + column-config: + type: object + properties: + name: + description: Name of the Column-Config to be used when resolving with Upload. + type: string + label: + description: Map of Localized labels. + type: object + items: + type: string + description: + description: Map of Localized descriptions. + type: object + items: + type: string + field: + type: string + description: Name of column in csv + additionalProperties: + properties: + pad: + description: Pad-String when uploading data, to avoid problems with some tools truncating leading zeros or similar. + type: string + length: + description: In conjunction with pad, the length of the padded string. + type: integer + resolvable: + description: True, if the Column should be printed to output. This can be used to have resolvable but not printable fields in mapping. + type: boolean + print: + description: True, if the Column should be printed to output. This can be used to have resolvable but not printable fields in mapping. + type: boolean + fillAnon: + description: True, if the field will be used to fill with anonymized ids, when the user is not allowed seeing real ids. + type: boolean + upload-config: + description: Describes the format of ids for EXTERNAL and entity-preview id kinds. Also describes the output format for ids in the result files. + type: object + properties: + ids: + type: array + items: + $ref: "#/components/schemas/column-config" + + currency-config: + description: Describes how the frontend shall interpret and display currency values. + type: object + properties: + prefix: + type: string + thousandSeparator: + type: string + decimalSeparator: + type: string + decimalScale: + type: string + + frontend-config: + type: object + properties: + version: + type: string + manualUrl: + type: string + format: url + contactEmail: + $ref: "#/components/schemas/mail" + currency: + $ref: "#/components/schemas/currency-config" + upload: + $ref: "#/components/schemas/upload-config" + id: + type: string + error: + type: object + properties: + message: + type: string + code: + type: string + id-label: + type: object + properties: + id: + $ref: "#/components/schemas/id" + label: + type: string + datasetAbility: + type: object + properties: + canUpload: + type: boolean + me: + type: object + properties: + userName: + type: string + hideLogoutButton: + type: boolean + datasetAbilities: + description: per dataset abilities of the logged in User. + type: object + additionalProperties: + $ref: "#/components/schemas/datasetAbility" + groups: + type: array + items: + $ref: "#/components/schemas/id-label" + +security: + - BasicAuth: [] + - Bearer: [] + +servers: + - url: http://lyo-peva02:8080/api +paths: + /frontend/config: + get: + summary: An Endpoint describing configuration for the frontend. + operationId: get-frontend-config + responses: + "200": + description: The Configuration for the Frontend + content: + application/json: + schema: + $ref: "#/components/schemas/frontend-config" + "401": + $ref: "#/components/responses/unauthorized" + + /me: + get: + operationId: get-me + summary: Fetches description of the currently logged in user + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/me" + description: Description of the user + "401": + $ref: "#/components/responses/unauthorized" From 24bed104c0aaf06994e5d98e5b141f4d2da2a853 Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Wed, 16 Nov 2022 10:48:48 +0100 Subject: [PATCH 02/86] more work on openapi.yaml --- .../resources/api/ConceptResource.java | 4 +- openapi.yaml | 90 +++++++++++++++++-- 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptResource.java index bceb2c9203..02e515c088 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptResource.java @@ -29,6 +29,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import lombok.ToString; @Produces({ ExtraMimeTypes.JSON_STRING, ExtraMimeTypes.SMILE_STRING }) @Consumes({ ExtraMimeTypes.JSON_STRING, ExtraMimeTypes.SMILE_STRING }) @@ -56,7 +57,7 @@ public Response getNode() { public ResolvedConceptsResult resolve(@NotNull ConceptCodeList conceptCodes) { List codes = conceptCodes.getConcepts().stream().map(String::trim).collect(Collectors.toList()); - if (concept instanceof TreeConcept) { + if (concept instanceof TreeConcept treeConcept && treeConcept.countElements() > 1) { return processor.resolveConceptElements((TreeConcept) concept, codes); } throw new WebApplicationException("can only resolved elements on tree concepts", Status.BAD_REQUEST); @@ -66,6 +67,7 @@ public ResolvedConceptsResult resolve(@NotNull ConceptCodeList conceptCodes) { @Getter @Setter @RequiredArgsConstructor(onConstructor_ = @JsonCreator) + @ToString public static class ConceptCodeList { private final List concepts; } diff --git a/openapi.yaml b/openapi.yaml index d5b0415059..bc9a89b4f5 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.3 +openapi: 3.0.0 info: version: 1.0.0 title: Conquery API @@ -6,11 +6,6 @@ info: license: name: TODO url: http://TODO/ -securityDefinitions: - Bearer: - type: apiKey - name: Authorization - in: header components: securitySchemes: BasicAuth: @@ -28,6 +23,54 @@ components: $ref: "#/components/schemas/error" description: The user is not logged in. schemas: + id: + type: string + tableId: + $ref: "#/components/schemas/id" + filterId: + $ref: "#/components/schemas/id" + conceptId: + $ref: "#/components/schemas/id" + executionId: + $ref: "#/components/schemas/id" + + feValue: + type: object + properties: + value: + type: string + label: + type: string + optionValue: + type: string + required: + - "value" + resolved-filter-result: + type: object + properties: + tableId: + $ref: "#/components/schemas/tableId" + filterId: + $ref: "#/components/schemas/filterId" + value: + type: array + items: + $ref: "#/components/schemas/feValue" + resolved-concepts-result: + type: object + properties: + resolvedConcepts: + type: array + uniqueItems: true + items: + type: string + unkownCodes: + type: array + uniqueItems: true + items: + type: string + resolvedFilter: + $ref: "#/components/responses/resolved-filter-result" mail: type: string format: mail @@ -112,6 +155,14 @@ components: type: string code: type: string + concept-code-list: + type: object + properties: + concepts: + type: array + items: + type: string + id-label: type: object properties: @@ -175,3 +226,30 @@ paths: description: Description of the user "401": $ref: "#/components/responses/unauthorized" + + /datasets/{dataset}/concepts/{concept}/resolve/: + post: + operationId: resolve-concepts + summary: Resolve values to concept-ids + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/concept-code-list" + responses: + '400': + description: When the requested Concept does not have children. + content: + application/json: + schema: + $ref: "#/components/responses/unauthorized" + '200': + description: Resolved Concepts + content: + application/json: + schema: + $ref: "#/components/responses/unauthorized" + + + From 32d3b032c452d3e1b45df79d770863be6f1492c8 Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Mon, 21 Nov 2022 10:48:59 +0100 Subject: [PATCH 03/86] fixes reference of schemsa --- openapi.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index bc9a89b4f5..a6d37c3724 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -70,7 +70,7 @@ components: items: type: string resolvedFilter: - $ref: "#/components/responses/resolved-filter-result" + $ref: "#/components/schemas/resolved-filter-result" mail: type: string format: mail @@ -146,8 +146,6 @@ components: $ref: "#/components/schemas/currency-config" upload: $ref: "#/components/schemas/upload-config" - id: - type: string error: type: object properties: @@ -240,16 +238,12 @@ paths: responses: '400': description: When the requested Concept does not have children. - content: - application/json: - schema: - $ref: "#/components/responses/unauthorized" '200': description: Resolved Concepts content: application/json: schema: - $ref: "#/components/responses/unauthorized" + $ref: "#/components/schemas/resolved-concepts-result" From b9c1b5f7058fd7bf0c4e37930a4ef92a4bc3761d Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Mon, 21 Nov 2022 14:24:51 +0100 Subject: [PATCH 04/86] Update openapi.yaml --- openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index a6d37c3724..4d671ca6bf 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -195,7 +195,7 @@ security: - Bearer: [] servers: - - url: http://lyo-peva02:8080/api + - url: http://localhost:8080/api paths: /frontend/config: get: From c2d5e6bdb63719e8c1dcbf040681672e4fd4c818 Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Tue, 13 Dec 2022 11:06:34 +0100 Subject: [PATCH 05/86] more work towards openapi --- openapi.yaml | 480 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 473 insertions(+), 7 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index a6d37c3724..312cd41689 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -7,6 +7,7 @@ info: name: TODO url: http://TODO/ components: + securitySchemes: BasicAuth: type: http @@ -14,7 +15,13 @@ components: Ingef: type: http scheme: bearer - + parameters: + dataset: + in: path + name: dataset + required: true + schema: + $ref: "#/components/schemas/datasetId" responses: unauthorized: content: @@ -29,10 +36,44 @@ components: $ref: "#/components/schemas/id" filterId: $ref: "#/components/schemas/id" + datasetId: + $ref: "#/components/schemas/id" + selectId: + $ref: "#/components/schemas/id" conceptId: $ref: "#/components/schemas/id" + connectorId: + $ref: "#/components/schemas/id" + secondaryIdId: + $ref: "#/components/schemas/id" + columnId: + $ref: "#/components/schemas/id" executionId: $ref: "#/components/schemas/id" + userId: + $ref: "#/components/schemas/id" + dateTime: #TODO + type: string + format: date-time + date: #TODO + type: string + format: date + query-state: + type: string + enum: + - NEW + - RUNNING + - FAILED + - DONE + + fe-dataset-id: + type: object + properties: + id: + $ref: "#/components/schemas/datasetId" + label: + type: string + feValue: type: object @@ -190,12 +231,265 @@ components: items: $ref: "#/components/schemas/id-label" + query-status: + type: object + properties: + label: + type: string + isPristineLabel: + type: boolean + tags: + type: array + items: + type: string + uniqueItems: true + createdAt: + $ref: "#/components/schemas/dateTime" + lastUsed:: + $ref: "#/components/schemas/dateTime" + owner: + $ref: "#/components/schemas/userId" + + + ownerName: + type: string + shared: + type: boolean + own: + type: boolean + system: + type: boolean + id: + $ref: "#/components/schemas/executionId" + status: + $ref: "#/components/schemas/query-state" + numberOfResults: + type: integer + requiredTime: + type: integer + startTime: + $ref: "#/components/schemas/dateTime" + finishTime: + $ref: "#/components/schemas/dateTime" + queryType: + type: string # TODO use discriminator from Query + secondaryId: + $ref: "#/components/schemas/secondaryIdId" + resultUrls: + type: array + items: + type: string #TODO url? + + labeled: + type: object + properties: + name: + type: string + label: + type: string + + secondaryId: + allOf: + - $ref: "#/components/schemas/labeled" + - type: object + properties: + description: + type: string + column-type: + oneOf: + - type: string + enum: + - STRING + - INTEGER + - BOOLEAN + - REAL + - DECIMAL + - MONEY + - DATE + - DATE_RANGE + - type: string + format: "LIST[.*]" + + semantic-event-date: #TODO descriptions for all + type: object + semantic-sources: + type: object + semantic-categorical: + type: object + semantic-group: + type: object + semantic-hidden: + type: object + semantic-select: + type: object + properties: + select: + $ref: "#/components/schemas/selectId" + semantic-concept-column: + type: object + properties: + concept: + $ref: "#/components/schemas/conceptId" + semantic-column: + type: object + properties: + concept: + $ref: "#/components/schemas/columnId" + + semantic-id: + type: object + properties: + kind: + type: string + + semantic-secondaryId: + type: object + properties: + secondaryId: + $ref: "#/components/schemas/secondaryIdId" + + semantics: + type: object + discriminator: + propertyName: type + mapping: + EVENT_DATE: "#/components/schemas/semantic-event-date" + SOURCES: "#/components/schemas/semantic-sources" + CATEGORICAL: "#/components/schemas/semantic-categorical" + GROUP: "#/components/schemas/semantic-group" + HIDDEN: "#/components/schemas/semantic-hidden" + SELECT: "#/components/schemas/semantic-select" + CONCEPT_COLUMN: "#/components/schemas/semantic-concept-column" + COLUMN: "#/components/schemas/semantic-column" + allOf: + - type: object + properties: + type: + type: string + oneOf: + - $ref: "#/components/schemas/semantic-event-date" + - $ref: "#/components/schemas/semantic-sources" + - $ref: "#/components/schemas/semantic-categorical" + - $ref: "#/components/schemas/semantic-group" + - $ref: "#/components/schemas/semantic-hidden" + - $ref: "#/components/schemas/semantic-select" + - $ref: "#/components/schemas/semantic-concept-column" + - $ref: "#/components/schemas/semantic-column" + + + + column-description: + type: object + properties: + label: + type: string + description: + type: string + defaultLabel: + type: string + type: + $ref: "#/components/schemas/column-type" + semantics: + type: array + uniqueItems: true + items: + $ref: "#/components/schemas/semantics" + date-range: + type: object + properties: + min: + $ref: "#/components/schemas/date" + max: + $ref: "#/components/schemas/date" + query: + type: object + #TODO but in separate file + error-info: + readOnly: true + type: object + properties: + id: + type: string + format: uuid + code: + type: string + message: + type: string + context: + type: object + additionalProperties: true + + entity-priview-config: + readOnly: true + type: object + properties: + all: + type: array + items: + properties: + name: + type: string + label: + type: string + default: + type: array + items: + properties: + name: + type: string + label: + type: string + + entity-history-execution-status: + allOf: + - $ref: "#/components/schemas/full-execution-status" + - type: object + properties: + infos: + type: array + items: + type: object + properties: + label: + type: string + description: + type: string + typeInfo: + type: string + value: + type: string + semantics: + type: array + items: + $ref: "#/components/schemas/semantics" + + + full-execution-status: + allOf: + - $ref: "#/components/schemas/query-status" + - type: object + properties: + progress: + type: number + minimum: 0 + maximum: 1 + columnDescriptions: + type: array + items: + $ref: "#/components/schemas/column-description" + canExpand: + type: boolean + query: + $ref: "#/components/schemas/query" #TODO + error: + $ref: "#/components/schemas/error-info" + availableSecondaryIds: + type: array + items: + $ref: "#/components/schemas/secondaryId" security: - BasicAuth: [] - Bearer: [] - -servers: - - url: http://lyo-peva02:8080/api paths: /frontend/config: get: @@ -210,6 +504,19 @@ paths: $ref: "#/components/schemas/frontend-config" "401": $ref: "#/components/responses/unauthorized" + /datasets/{dataset}/entity-preview: + parameters: + - $ref: "#/components/parameters/dataset" + get: + operationId: get-entity-preview-config + description: Gets entity-preview config and defaults for frontend. + responses: + '200': + description: The config. + content: + application/json: + schema: + $ref: "#/components/schemas/entity-priview-config" /me: get: @@ -224,8 +531,28 @@ paths: description: Description of the user "401": $ref: "#/components/responses/unauthorized" - + /datasets: + get: + operationId: list-datasets + description: Lists all datasets available to the User. + responses: + '200': + description: The available datasets. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/fe-dataset-id" /datasets/{dataset}/concepts/{concept}/resolve/: + parameters: + - $ref: "#/components/parameters/dataset" + - in: path + name: concept + required: true + schema: + $ref: "#/components/schemas/conceptId" + post: operationId: resolve-concepts summary: Resolve values to concept-ids @@ -245,5 +572,144 @@ paths: schema: $ref: "#/components/schemas/resolved-concepts-result" - - + /datasets/{dataset}/queries: + parameters: + - $ref: "#/components/parameters/dataset" + get: + operationId: get-query-status + summary: Get Status and metadata of Queries + responses: + '400': + description: The Status and Metadata. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/query-status" + post: + operationId: post-query + description: Submit a query for execution in the dataset. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/query" + responses: + '400': + description: Status of the submitted query. + content: + application/json: + schema: + $ref: "#/components/schemas/full-execution-status" + + /datasets/{dataset}/queries/{query}: + parameters: + - $ref: "#/components/parameters/dataset" + - in: path + name: query + required: true + schema: + $ref: "#/components/schemas/executionId" + + get: + operationId: get-query-status + summary: Get Status and metadata of Query + responses: + '400': + description: The Status and Metadata. + content: + application/json: + schema: + $ref: "#/components/schemas/full-execution-status" + patch: + description: update a query. + operationId: patch-query + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/query" + responses: + '200': + description: Desription of the updated execution + content: + application/json: + schema: + $ref: "#/components/schemas/full-execution-status" + delete: + operationId: delete-query + description: delete the query. + responses: + '200': + description: Query was deleted. + /datasets/{dataset}/queries/{query}/cancel: + parameters: + - in: path + name: dataset + required: true + schema: + $ref: "#/components/schemas/datasetId" + - in: path + name: query + required: true + schema: + $ref: "#/components/schemas/executionId" + post: + operationId: cancel-query + responses: + '200': + description: Query was cancelled. + /datasets/{dataset}/queries/{query}/reexecute: + parameters: + - $ref: "#/components/parameters/dataset" + - in: path + name: query + required: true + schema: + $ref: "#/components/schemas/executionId" + post: + operationId: reexecute-query + responses: + '200': + description: Description of restarted Query. + content: + application/json: + schema: + $ref: "#/components/schemas/query" + + /datasets/{dataset}/queries/entity: + parameters: + - $ref: "#/components/parameters/dataset" + post: + operationId: get-entity-preview + description: Fetch a history of a single entity + requestBody: + content: + application/json: + schema: + type: object + properties: + idKind: + type: string + entityId: + type: string + time: + $ref: "#/components/schemas/date-range" + sources: + type: array + items: + $ref: "#/components/schemas/connectorId" + responses: + '200': + description: description of the history of the entity. + content: + application/json: + schema: + $ref: "#/components/schemas/full-execution-status" + + + + + + \ No newline at end of file From 68c4e5b8cf61914ba17dedaf6730593228ea240f Mon Sep 17 00:00:00 2001 From: Fabian Kovacs Date: Tue, 13 Dec 2022 11:08:23 +0100 Subject: [PATCH 06/86] fixes for openapi. --- openapi.yaml | 74 +++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index bc9a89b4f5..8086330566 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,9 +1,10 @@ -openapi: 3.0.0 +openapi: 3.1.0 info: version: 1.0.0 title: Conquery API description: Conquery API license: + identifier: TODO name: TODO url: http://TODO/ components: @@ -11,9 +12,6 @@ components: BasicAuth: type: http scheme: basic - Ingef: - type: http - scheme: bearer responses: unauthorized: @@ -27,6 +25,8 @@ components: type: string tableId: $ref: "#/components/schemas/id" + datasetId: + $ref: "#/components/schemas/id" filterId: $ref: "#/components/schemas/id" conceptId: @@ -46,7 +46,7 @@ components: required: - "value" resolved-filter-result: - type: object + type: [object, null] properties: tableId: $ref: "#/components/schemas/tableId" @@ -70,7 +70,8 @@ components: items: type: string resolvedFilter: - $ref: "#/components/responses/resolved-filter-result" + $ref: "#/components/schemas/resolved-filter-result" + mail: type: string format: mail @@ -93,23 +94,22 @@ components: field: type: string description: Name of column in csv - additionalProperties: - properties: - pad: - description: Pad-String when uploading data, to avoid problems with some tools truncating leading zeros or similar. - type: string - length: - description: In conjunction with pad, the length of the padded string. - type: integer - resolvable: - description: True, if the Column should be printed to output. This can be used to have resolvable but not printable fields in mapping. - type: boolean - print: - description: True, if the Column should be printed to output. This can be used to have resolvable but not printable fields in mapping. - type: boolean - fillAnon: - description: True, if the field will be used to fill with anonymized ids, when the user is not allowed seeing real ids. - type: boolean + pad: + description: Pad-String when uploading data, to avoid problems with some tools truncating leading zeros or similar. + type: string + length: + description: In conjunction with pad, the length of the padded string. + type: integer + resolvable: + description: True, if the Column should be printed to output. This can be used to have resolvable but not printable fields in mapping. + type: boolean + print: + description: True, if the Column should be printed to output. This can be used to have resolvable but not printable fields in mapping. + type: boolean + fillAnon: + description: True, if the field will be used to fill with anonymized ids, when the user is not allowed seeing real ids. + type: boolean + upload-config: description: Describes the format of ids for EXTERNAL and entity-preview id kinds. Also describes the output format for ids in the result files. type: object @@ -146,15 +146,13 @@ components: $ref: "#/components/schemas/currency-config" upload: $ref: "#/components/schemas/upload-config" - id: - type: string error: type: object properties: message: type: string code: - type: string + type: number concept-code-list: type: object properties: @@ -229,6 +227,17 @@ paths: /datasets/{dataset}/concepts/{concept}/resolve/: post: + parameters: + - in: path + name: dataset + required: true + schema: + $ref: "#/components/schemas/datasetId" + - in: path + name: concept + required: true + schema: + $ref: "#/components/schemas/conceptId" operationId: resolve-concepts summary: Resolve values to concept-ids requestBody: @@ -239,17 +248,18 @@ paths: $ref: "#/components/schemas/concept-code-list" responses: '400': - description: When the requested Concept does not have children. - content: - application/json: - schema: - $ref: "#/components/responses/unauthorized" + description: When the requested Concept does not have children. + content: + application/json: + schema: + $ref: "#/components/schemas/error" '200': description: Resolved Concepts content: application/json: schema: - $ref: "#/components/responses/unauthorized" + $ref: "#/components/schemas/resolved-concepts-result" + From 31efc6ca93724e8d8fea51a8db7957736d832055 Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Tue, 13 Dec 2022 11:11:50 +0100 Subject: [PATCH 07/86] remove dupe --- openapi.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index a3a3c097c5..07b4ee4601 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -40,8 +40,6 @@ components: $ref: "#/components/schemas/id" filterId: $ref: "#/components/schemas/id" - datasetId: - $ref: "#/components/schemas/id" selectId: $ref: "#/components/schemas/id" conceptId: From 801cdb72be1f392bf8523d23d60f03db6a0c74ee Mon Sep 17 00:00:00 2001 From: Fabian Kovacs Date: Tue, 13 Dec 2022 16:32:07 +0100 Subject: [PATCH 08/86] Implementation of FullExecutionStatus --- openapi.yaml | 260 ++++++++++++++++++++++++++++----------------------- 1 file changed, 144 insertions(+), 116 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 07b4ee4601..e3f5273ea6 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,12 +1,11 @@ -openapi: 3.1.0 +openapi: 3.0.0 info: version: 1.0.0 title: Conquery API description: Conquery API license: - identifier: TODO name: TODO - url: http://TODO/ + url: http://TODO/ components: securitySchemes: @@ -24,6 +23,18 @@ components: required: true schema: $ref: "#/components/schemas/datasetId" + query: + in: path + name: query + required: true + schema: + $ref: "#/components/schemas/executionId" + concept: + in: path + name: concept + required: true + schema: + $ref: "#/components/schemas/conceptId" responses: unauthorized: content: @@ -56,7 +67,7 @@ components: $ref: "#/components/schemas/id" dateTime: #TODO type: string - format: date-time + # format: date-time TODO not compatible: Value does not conform to the required ISO-8601 datetime format. Invalid value '2022-12-13T13:33:32.455901838Z' for type datetime at date: #TODO type: string format: date @@ -67,7 +78,10 @@ components: - RUNNING - FAILED - DONE - + fe-dataset-ids: + type: array + items: + $ref: "#/components/schemas/fe-dataset-id" fe-dataset-id: type: object properties: @@ -76,7 +90,6 @@ components: label: type: string - feValue: type: object properties: @@ -89,7 +102,7 @@ components: required: - "value" resolved-filter-result: - type: [object, null] + type: object properties: tableId: $ref: "#/components/schemas/tableId" @@ -246,12 +259,12 @@ components: uniqueItems: true createdAt: $ref: "#/components/schemas/dateTime" - lastUsed:: - $ref: "#/components/schemas/dateTime" + lastUsed: + oneOf: + - $ref: "#/components/schemas/dateTime" + - type: 'null' owner: $ref: "#/components/schemas/userId" - - ownerName: type: string shared: @@ -267,15 +280,21 @@ components: numberOfResults: type: integer requiredTime: - type: integer + oneOf: + - type: integer + - type: 'null' startTime: - $ref: "#/components/schemas/dateTime" + oneOf: + - $ref: "#/components/schemas/dateTime" + - type: 'null' finishTime: $ref: "#/components/schemas/dateTime" queryType: type: string # TODO use discriminator from Query secondaryId: - $ref: "#/components/schemas/secondaryIdId" + oneOf: + - $ref: "#/components/schemas/secondaryIdId" + - type: 'null' resultUrls: type: array items: @@ -303,91 +322,115 @@ components: - STRING - INTEGER - BOOLEAN - - REAL - - DECIMAL + - NUMERIC - MONEY - DATE - DATE_RANGE - type: string - format: "LIST[.*]" + pattern: "LIST\\[.*\\]" semantic-event-date: #TODO descriptions for all - type: object + properties: + type: + type: string + required: + - "type" semantic-sources: - type: object + properties: + type: + type: string + required: + - "type" semantic-categorical: - type: object + properties: + type: + type: string + required: + - "type" semantic-group: - type: object + properties: + type: + type: string + required: + - "type" semantic-hidden: - type: object + properties: + type: + type: string + required: + - "type" semantic-select: - type: object properties: - select: - $ref: "#/components/schemas/selectId" + type: + type: string + select: + $ref: "#/components/schemas/selectId" + required: + - "type" semantic-concept-column: - type: object properties: - concept: - $ref: "#/components/schemas/conceptId" + type: + type: string + concept: + $ref: "#/components/schemas/conceptId" + required: + - "type" semantic-column: - type: object properties: - concept: + type: + type: string + column: $ref: "#/components/schemas/columnId" - + required: + - "type" + semantic-id: - type: object properties: + type: + type: string kind: type: string - + required: + - "type" + + semantic-secondaryId: - type: object properties: + type: + type: string secondaryId: $ref: "#/components/schemas/secondaryIdId" - + required: + - "type" semantics: - type: object + # NOTE: Documentation asks us to provide both mapping and oneOf, that does seem to be wrong and cannot be deserialized. discriminator: - propertyName: type + propertyName: "type" mapping: - EVENT_DATE: "#/components/schemas/semantic-event-date" - SOURCES: "#/components/schemas/semantic-sources" - CATEGORICAL: "#/components/schemas/semantic-categorical" - GROUP: "#/components/schemas/semantic-group" - HIDDEN: "#/components/schemas/semantic-hidden" - SELECT: "#/components/schemas/semantic-select" - CONCEPT_COLUMN: "#/components/schemas/semantic-concept-column" - COLUMN: "#/components/schemas/semantic-column" - allOf: - - type: object - properties: - type: - type: string + "CATEGORICAL": "#/components/schemas/semantic-categorical" + "COLUMN": "#/components/schemas/semantic-column" + "CONCEPT_COLUMN": "#/components/schemas/semantic-concept-column" + "EVENT_DATE": "#/components/schemas/semantic-event-date" + "GROUP": "#/components/schemas/semantic-group" + "HIDDEN": "#/components/schemas/semantic-hidden" + "ID": "#/components/schemas/semantic-id" + "SELECT": "#/components/schemas/semantic-select" + "SOURCES": "#/components/schemas/semantic-sources" + + + nullable-string: oneOf: - - $ref: "#/components/schemas/semantic-event-date" - - $ref: "#/components/schemas/semantic-sources" - - $ref: "#/components/schemas/semantic-categorical" - - $ref: "#/components/schemas/semantic-group" - - $ref: "#/components/schemas/semantic-hidden" - - $ref: "#/components/schemas/semantic-select" - - $ref: "#/components/schemas/semantic-concept-column" - - $ref: "#/components/schemas/semantic-column" - - - + - type: 'null' + - type: string column-description: type: object properties: label: type: string description: - type: string + $ref: "#/components/schemas/nullable-string" defaultLabel: - type: string + $ref: "#/components/schemas/nullable-string" type: $ref: "#/components/schemas/column-type" semantics: @@ -404,6 +447,7 @@ components: $ref: "#/components/schemas/date" query: type: object + additionalProperties: true #TODO but in separate file error-info: readOnly: true @@ -440,7 +484,21 @@ components: type: string label: type: string - + entity-history-execution-status-info: + type: object + properties: + label: + type: string + description: + type: string + typeInfo: + type: string + value: + type: string + semantics: + type: array + items: + $ref: "#/components/schemas/semantics" entity-history-execution-status: allOf: - $ref: "#/components/schemas/full-execution-status" @@ -449,31 +507,18 @@ components: infos: type: array items: - type: object - properties: - label: - type: string - description: - type: string - typeInfo: - type: string - value: - type: string - semantics: - type: array - items: - $ref: "#/components/schemas/semantics" - - + $ref: "#/components/schemas/entity-history-execution-status-info" full-execution-status: allOf: - $ref: "#/components/schemas/query-status" - type: object properties: progress: - type: number - minimum: 0 - maximum: 1 + oneOf: + - type: number + minimum: 0 + maximum: 1 + - type: 'null' columnDescriptions: type: array items: @@ -483,15 +528,13 @@ components: query: $ref: "#/components/schemas/query" #TODO error: - $ref: "#/components/schemas/error-info" + oneOf: + - $ref: "#/components/schemas/error-info" + - type: 'null' availableSecondaryIds: type: array items: - $ref: "#/components/schemas/secondaryId" -security: - - BasicAuth: [] - - Bearer: [] - + $ref: "#/components/schemas/secondaryIdId" servers: - url: http://localhost:8080/api paths: @@ -545,18 +588,10 @@ paths: content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/fe-dataset-id" - /datasets/{dataset}/concepts/{concept}/resolve/: - parameters: - - $ref: "#/components/parameters/dataset" - - in: path - name: concept - required: true - schema: - $ref: "#/components/schemas/conceptId" - + $ref: "#/components/schemas/fe-dataset-ids" + + + /concepts/{concept}/resolve/: post: parameters: - in: path @@ -595,10 +630,10 @@ paths: parameters: - $ref: "#/components/parameters/dataset" get: - operationId: get-query-status + operationId: list-queries summary: Get Status and metadata of Queries responses: - '400': + '200': description: The Status and Metadata. content: application/json: @@ -615,16 +650,15 @@ paths: schema: $ref: "#/components/schemas/query" responses: - '400': + '200': description: Status of the submitted query. content: application/json: schema: $ref: "#/components/schemas/full-execution-status" - /datasets/{dataset}/queries/{query}: + /queries/{query}: parameters: - - $ref: "#/components/parameters/dataset" - in: path name: query required: true @@ -635,10 +669,10 @@ paths: operationId: get-query-status summary: Get Status and metadata of Query responses: - '400': + '200': description: The Status and Metadata. content: - application/json: + application/json;charset=utf-8: schema: $ref: "#/components/schemas/full-execution-status" patch: @@ -662,13 +696,8 @@ paths: responses: '200': description: Query was deleted. - /datasets/{dataset}/queries/{query}/cancel: + /queries/{query}/cancel: parameters: - - in: path - name: dataset - required: true - schema: - $ref: "#/components/schemas/datasetId" - in: path name: query required: true @@ -679,9 +708,8 @@ paths: responses: '200': description: Query was cancelled. - /datasets/{dataset}/queries/{query}/reexecute: + /queries/{query}/reexecute: parameters: - - $ref: "#/components/parameters/dataset" - in: path name: query required: true From 6462883a3b314b69a3b34fb17f57754d03ab8671 Mon Sep 17 00:00:00 2001 From: Fabian Kovacs Date: Thu, 15 Dec 2022 19:22:33 +0100 Subject: [PATCH 09/86] Proper implementation of discriminator&oneOf for semantics --- openapi.yaml | 133 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 47 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index e3f5273ea6..be7a985945 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -330,80 +330,109 @@ components: pattern: "LIST\\[.*\\]" semantic-event-date: #TODO descriptions for all + allOf: + - $ref: "#/components/schemas/semantic-base" properties: - type: - type: string - required: - - "type" + type: + type: string + enum: + - "EVENT_DATE" semantic-sources: + allOf: + - $ref: "#/components/schemas/semantic-base" properties: type: type: string - required: - - "type" + enum: + - "SOURCES" semantic-categorical: + allOf: + - $ref: "#/components/schemas/semantic-base" properties: type: type: string - required: - - "type" + enum: + - "CATEGORICAL" semantic-group: + allOf: + - $ref: "#/components/schemas/semantic-base" properties: - type: - type: string - required: - - "type" + type: + type: string + enum: + - "GROUP" semantic-hidden: + allOf: + - $ref: "#/components/schemas/semantic-base" properties: - type: - type: string - required: - - "type" + type: + type: string + enum: + - "HIDDEN" semantic-select: properties: - type: - type: string + type: + type: string + enum: + - "SELECT" + allOf: + - $ref: "#/components/schemas/semantic-base" + - type: object + properties: select: $ref: "#/components/schemas/selectId" - required: - - "type" semantic-concept-column: properties: - type: - type: string + type: + type: string + enum: + - "CONCEPT_COLUMN" + allOf: + - $ref: "#/components/schemas/semantic-base" + - type: object + properties: concept: $ref: "#/components/schemas/conceptId" - required: - - "type" semantic-column: properties: - type: - type: string - column: - $ref: "#/components/schemas/columnId" - required: - - "type" - + type: + type: string + enum: + - "COLUMN" + allOf: + - $ref: "#/components/schemas/semantic-base" + - type: object + properties: + column: + $ref: "#/components/schemas/columnId" + semantic-id: properties: type: type: string - kind: - type: string - required: - - "type" - - + enum: + - "ID" + allOf: + - $ref: "#/components/schemas/semantic-base" + - type: object + properties: + kind: + type: string semantic-secondaryId: + allOf: + - $ref: "#/components/schemas/semantic-base" + - type: object + properties: + secondaryId: + $ref: "#/components/schemas/secondaryIdId" + semantic-base: + type: object properties: type: type: string - secondaryId: - $ref: "#/components/schemas/secondaryIdId" required: - "type" semantics: - # NOTE: Documentation asks us to provide both mapping and oneOf, that does seem to be wrong and cannot be deserialized. discriminator: propertyName: "type" mapping: @@ -416,21 +445,31 @@ components: "ID": "#/components/schemas/semantic-id" "SELECT": "#/components/schemas/semantic-select" "SOURCES": "#/components/schemas/semantic-sources" - - - nullable-string: oneOf: - - type: 'null' - - type: string + - $ref: "#/components/schemas/semantic-categorical" + - $ref: "#/components/schemas/semantic-column" + - $ref: "#/components/schemas/semantic-concept-column" + - $ref: "#/components/schemas/semantic-event-date" + - $ref: "#/components/schemas/semantic-group" + - $ref: "#/components/schemas/semantic-hidden" + - $ref: "#/components/schemas/semantic-id" + - $ref: "#/components/schemas/semantic-select" + - $ref: "#/components/schemas/semantic-sources" + + column-description: type: object properties: label: type: string description: - $ref: "#/components/schemas/nullable-string" + oneOf: + - type: 'null' + - type: string defaultLabel: - $ref: "#/components/schemas/nullable-string" + oneOf: + - type: 'null' + - type: string type: $ref: "#/components/schemas/column-type" semantics: From 644f4c16a92f8309117dc696a1b4f5373d8f2d5f Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:46:45 +0100 Subject: [PATCH 10/86] fixes typos --- openapi.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index be7a985945..5176b1fec2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -120,7 +120,7 @@ components: uniqueItems: true items: type: string - unkownCodes: + unknownCodes: type: array uniqueItems: true items: @@ -503,7 +503,7 @@ components: type: object additionalProperties: true - entity-priview-config: + entity-preview-config: readOnly: true type: object properties: From c6abd012afd26720c2cc68fdc7fd3686f7ac77f1 Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:52:03 +0100 Subject: [PATCH 11/86] cleanup after linting --- openapi.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 5176b1fec2..7393256bef 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -630,14 +630,9 @@ paths: $ref: "#/components/schemas/fe-dataset-ids" - /concepts/{concept}/resolve/: + /concepts/{concept}/resolve: post: parameters: - - in: path - name: dataset - required: true - schema: - $ref: "#/components/schemas/datasetId" - in: path name: concept required: true From 52ca62e5652bce51a33d0e79f8814644accdc908 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Mon, 2 Jan 2023 15:35:18 +0100 Subject: [PATCH 12/86] Adjust import description --- frontend/src/localization/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/localization/de.json b/frontend/src/localization/de.json index 8d717472da..700a65069f 100644 --- a/frontend/src/localization/de.json +++ b/frontend/src/localization/de.json @@ -492,7 +492,7 @@ }, "importModal": { "headline": "Zeilenweise Importieren", - "subtitle": "Neben Drag & Drop können Werte zeilenweise importiert werden, zum Beispiel aus der Zwischenablage (Kopieren und Einfügen STRG+C / STRG+V).", + "subtitle": "Importiere Werte zeilenweise, zum Beispiel aus der Zwischenablage: Kopieren (STRG+C) und Einfügen (STRG+V). Wähle alternativ eine Datei im Textformat (TXT) aus, oder lege sie direkt im Textfeld ab.", "paste": "Aus Zwischenablage einfügen", "submit": "Übernehmen", "pasted": "Importiert" From 333ffe1de99c44a7b0cd6a08ff5536cd46cfcaf2 Mon Sep 17 00:00:00 2001 From: Max Thonagel <12283268+thoniTUB@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:09:42 +0100 Subject: [PATCH 13/86] adds locale serdes --- .../io/jackson/ConquerySerializersModule.java | 8 ++++- .../serializer/LocaleDeserializer.java | 36 +++++++++++++++++++ .../jackson/serializer/LocaleSerializer.java | 20 +++++++++++ .../conquery/models/config/ColumnConfig.java | 9 +---- .../conquery/models/SerializationTests.java | 15 ++++++++ 5 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java create mode 100644 backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleSerializer.java diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java index 7949705912..3434ee5bc8 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java +++ b/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.Currency; import java.util.List; +import java.util.Locale; import com.bakdata.conquery.io.cps.CPSTypeIdResolver; import com.bakdata.conquery.io.jackson.serializer.CharArrayBufferDeserializer; @@ -12,6 +13,8 @@ import com.bakdata.conquery.io.jackson.serializer.CurrencyUnitDeserializer; import com.bakdata.conquery.io.jackson.serializer.CurrencyUnitSerializer; import com.bakdata.conquery.io.jackson.serializer.IdKeyDeserializer; +import com.bakdata.conquery.io.jackson.serializer.LocaleDeserializer; +import com.bakdata.conquery.io.jackson.serializer.LocaleSerializer; import com.bakdata.conquery.models.identifiable.ids.Id; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.ValueInstantiator; @@ -54,11 +57,14 @@ public Object createUsingDefault(DeserializationContext ctxt) throws IOException .getSubclasses(Id.class.getName()) .loadClasses(); - for(Class type : idTypes) { + for (Class type : idTypes) { addKeyDeserializer(type, new IdKeyDeserializer<>()); } addSerializer(new ConqueryDoubleSerializer()); addDeserializer(CharArrayBuffer.class, new CharArrayBufferDeserializer()); addSerializer(CharArrayBuffer.class, new CharArrayBufferSerializer()); + + addDeserializer(Locale.class, new LocaleDeserializer()); + addSerializer(Locale.class, new LocaleSerializer()); } } \ No newline at end of file diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java new file mode 100644 index 0000000000..88ed676f72 --- /dev/null +++ b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java @@ -0,0 +1,36 @@ +package com.bakdata.conquery.io.jackson.serializer; + +import java.io.IOException; +import java.util.Locale; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import org.apache.commons.lang3.StringUtils; + +/** + * Parses a language tag as a locale using {@link Locale#forLanguageTag(String)}. + * Fails if the provided token is not a string token or the tag cannot be matched to a locale. + */ +public class LocaleDeserializer extends JsonDeserializer { + @Override + public Locale deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + final JsonToken jsonToken = p.currentToken(); + if (jsonToken != JsonToken.VALUE_STRING) { + throw MismatchedInputException.from(p, Locale.class, "Expected a string token but found: " + jsonToken); + } + + final String languageTag = p.getText(); + final Locale locale = Locale.forLanguageTag(languageTag); + + if (StringUtils.isNotBlank(languageTag) && !languageTag.equals(Locale.ROOT.toLanguageTag()) && locale == Locale.ROOT) { + // When Locale#forLanguageTag does not recognize the tag it defaults to Locale.ROOT + // This should only be intended if the tag was blank or "und" (undefined ~= Locale.ROOT.toLanguageTag()) + throw MismatchedInputException.from(p, Locale.class, "Unable to language tag '" + languageTag + "' to locale"); + } + + return locale; + } +} diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleSerializer.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleSerializer.java new file mode 100644 index 0000000000..0d0554c8c9 --- /dev/null +++ b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleSerializer.java @@ -0,0 +1,20 @@ +package com.bakdata.conquery.io.jackson.serializer; + +import java.io.IOException; +import java.util.Locale; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +/** + * Uses the language tag provided by {@link Locale#toLanguageTag()} to serialize a locale as a string token. + * Without this serializer Jackson would write out {@link Locale#GERMANY} as {@code \"de_DE\"}. + * This serializer writes the locale as {@code de-DE}. + */ +public class LocaleSerializer extends JsonSerializer { + @Override + public void serialize(Locale value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(value.toLanguageTag()); + } +} diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java index 659da93b77..702e281a43 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java @@ -71,7 +71,7 @@ public EntityIdMap.ExternalId read(String value) { * Map of Localized description. */ @Builder.Default - private Map description = Collections.emptyMap(); + private Map description = Collections.emptyMap(); /** * Name of column in csv for {@link AdminDatasetProcessor#setIdMapping(java.io.InputStream, com.bakdata.conquery.models.worker.Namespace)}. @@ -116,11 +116,4 @@ public EntityIdMap.ExternalId read(String value) { public boolean isLabelKeysLocale() { return getLabel().keySet().stream().map(Locale::forLanguageTag).noneMatch(Objects::isNull); } - - @JsonIgnore - @ValidationMethod(message = "Keys must be valid Locales.") - public boolean isDescriptionKeysLocale() { - return getDescription().keySet().stream().map(Locale::forLanguageTag).noneMatch(Objects::isNull); - } - } diff --git a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java index 6cff19072a..5ed7d47df8 100644 --- a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java +++ b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java @@ -8,6 +8,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.UUID; @@ -714,4 +715,18 @@ public void test(Range range) throws IOException, JSONException { .test(range); } + @Test + public void locale() throws JSONException, IOException { + SerializationTestUtil.forType(Locale.class) + .objectMappers(getApiMapper(), getManagerInternalMapper()) + .test(Locale.GERMANY); + } + + @Test + public void localeArray() throws JSONException, IOException { + SerializationTestUtil.forType(Locale[].class) + .objectMappers(getApiMapper(), getManagerInternalMapper()) + .test(new Locale[]{Locale.GERMANY, Locale.ROOT, Locale.ENGLISH, Locale.US, Locale.UK}); + } + } From e7b97579fb6bb1e0c4c7f49cb1ea498beea93832 Mon Sep 17 00:00:00 2001 From: MT <12283268+thoniTUB@users.noreply.github.com> Date: Fri, 16 Dec 2022 09:35:50 +0100 Subject: [PATCH 14/86] Update backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java thx Co-authored-by: awildturtok <1553491+awildturtok@users.noreply.github.com> --- .../conquery/io/jackson/serializer/LocaleDeserializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java index 88ed676f72..0129156ebc 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java +++ b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java @@ -28,7 +28,7 @@ public Locale deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx if (StringUtils.isNotBlank(languageTag) && !languageTag.equals(Locale.ROOT.toLanguageTag()) && locale == Locale.ROOT) { // When Locale#forLanguageTag does not recognize the tag it defaults to Locale.ROOT // This should only be intended if the tag was blank or "und" (undefined ~= Locale.ROOT.toLanguageTag()) - throw MismatchedInputException.from(p, Locale.class, "Unable to language tag '" + languageTag + "' to locale"); + throw MismatchedInputException.from(p, Locale.class, "Unable to map language tag '" + languageTag + "' to locale"); } return locale; From ed2e07b7e9199f34ada057422b8896f7c4966984 Mon Sep 17 00:00:00 2001 From: Max Thonagel <12283268+thoniTUB@users.noreply.github.com> Date: Mon, 9 Jan 2023 09:20:06 +0100 Subject: [PATCH 15/86] cache root language tag --- .../io/jackson/serializer/LocaleDeserializer.java | 5 ++++- .../bakdata/conquery/models/config/ColumnConfig.java | 11 +---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java index 0129156ebc..a5f101ec79 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java +++ b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/LocaleDeserializer.java @@ -15,6 +15,9 @@ * Fails if the provided token is not a string token or the tag cannot be matched to a locale. */ public class LocaleDeserializer extends JsonDeserializer { + + public static final String ROOT_LOCALE_TAG = Locale.ROOT.toLanguageTag(); + @Override public Locale deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { final JsonToken jsonToken = p.currentToken(); @@ -25,7 +28,7 @@ public Locale deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx final String languageTag = p.getText(); final Locale locale = Locale.forLanguageTag(languageTag); - if (StringUtils.isNotBlank(languageTag) && !languageTag.equals(Locale.ROOT.toLanguageTag()) && locale == Locale.ROOT) { + if (StringUtils.isNotBlank(languageTag) && !languageTag.equals(ROOT_LOCALE_TAG) && locale == Locale.ROOT) { // When Locale#forLanguageTag does not recognize the tag it defaults to Locale.ROOT // This should only be intended if the tag was blank or "und" (undefined ~= Locale.ROOT.toLanguageTag()) throw MismatchedInputException.from(p, Locale.class, "Unable to map language tag '" + languageTag + "' to locale"); diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java index 702e281a43..b923c7bad4 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/ColumnConfig.java @@ -3,17 +3,14 @@ import java.util.Collections; import java.util.Locale; import java.util.Map; -import java.util.Objects; import javax.validation.constraints.NotEmpty; import com.bakdata.conquery.io.jackson.View; import com.bakdata.conquery.models.identifiable.mapping.EntityIdMap; import com.bakdata.conquery.resources.admin.rest.AdminDatasetProcessor; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonView; import com.google.common.base.Strings; -import io.dropwizard.validation.ValidationMethod; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -65,7 +62,7 @@ public EntityIdMap.ExternalId read(String value) { */ @NotEmpty @Builder.Default - private Map label = Collections.emptyMap(); + private Map label = Collections.emptyMap(); /** * Map of Localized description. @@ -110,10 +107,4 @@ public EntityIdMap.ExternalId read(String value) { @Builder.Default @JsonView(View.Persistence.class) private boolean fillAnon = false; - - @JsonIgnore - @ValidationMethod(message = "Keys must be valid Locales.") - public boolean isLabelKeysLocale() { - return getLabel().keySet().stream().map(Locale::forLanguageTag).noneMatch(Objects::isNull); - } } From 30165df599ac860ecab331661d036009d2c7fd16 Mon Sep 17 00:00:00 2001 From: Max Thonagel <12283268+thoniTUB@users.noreply.github.com> Date: Mon, 9 Jan 2023 10:53:43 +0100 Subject: [PATCH 16/86] fix default value after type change --- .../com/bakdata/conquery/models/config/IdColumnConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/IdColumnConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/IdColumnConfig.java index a8292502da..b5b4ca1870 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/IdColumnConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/IdColumnConfig.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -51,7 +52,7 @@ public class IdColumnConfig { ColumnConfig.builder() .name("ID") .field("result") - .label(Map.of("", "result")) // "" ~= Locale.ROOT + .label(Map.of(Locale.ROOT, "result")) .resolvable(true) .fillAnon(true) .print(true) From d3b26e4452ff04930b5483272f65fac17bc1bbc6 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Mon, 9 Jan 2023 12:36:50 +0100 Subject: [PATCH 17/86] Start iterating entity ids --- frontend/src/js/entity-history/EntityIdsList.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/js/entity-history/EntityIdsList.tsx b/frontend/src/js/entity-history/EntityIdsList.tsx index 10de1c8bbd..13b44f8635 100644 --- a/frontend/src/js/entity-history/EntityIdsList.tsx +++ b/frontend/src/js/entity-history/EntityIdsList.tsx @@ -41,12 +41,18 @@ const EntityStatus = styled("div")` const TheEntityId = styled("div")<{ active?: boolean }>` font-weight: 700; `; + const Number = styled("div")` font-size: ${({ theme }) => theme.font.xs}; color: ${({ theme }) => theme.col.gray}; flex-shrink: 0; `; +const Gray = styled("span")` + font-weight: 300; + color: ${({ theme }) => theme.col.gray}; +`; + interface Props { currentEntityId: EntityId | null; entityIds: EntityId[]; @@ -77,7 +83,9 @@ export const EntityIdsList = ({ onClick={() => updateHistorySession({ entityId, years: [] })} > #{index + 1} - {entityId.id} + + {entityId.id} ({entityId.kind}) + {entityIdsStatus[entityId.id] && entityIdsStatus[entityId.id].map((val) => ( From fceabd184821dd954dfaf00853305f8514166678 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Mon, 9 Jan 2023 17:20:58 +0100 Subject: [PATCH 18/86] Fix layout --- frontend/src/js/entity-history/EntityIdsList.tsx | 1 + frontend/src/js/entity-history/Timeline.tsx | 2 +- frontend/src/js/entity-history/timeline/YearHead.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/js/entity-history/EntityIdsList.tsx b/frontend/src/js/entity-history/EntityIdsList.tsx index 13b44f8635..1d7da5ae22 100644 --- a/frontend/src/js/entity-history/EntityIdsList.tsx +++ b/frontend/src/js/entity-history/EntityIdsList.tsx @@ -40,6 +40,7 @@ const EntityStatus = styled("div")` `; const TheEntityId = styled("div")<{ active?: boolean }>` font-weight: 700; + flex-shrink: 0; `; const Number = styled("div")` diff --git a/frontend/src/js/entity-history/Timeline.tsx b/frontend/src/js/entity-history/Timeline.tsx index 52a8fc9d56..646f96a6ef 100644 --- a/frontend/src/js/entity-history/Timeline.tsx +++ b/frontend/src/js/entity-history/Timeline.tsx @@ -29,7 +29,7 @@ const Root = styled("div")` -webkit-overflow-scrolling: touch; padding: 0 20px 0 10px; display: inline-grid; - grid-template-columns: 100px auto; + grid-template-columns: 125px auto; grid-auto-rows: minmax(min-content, max-content); gap: 20px 4px; width: 100%; diff --git a/frontend/src/js/entity-history/timeline/YearHead.tsx b/frontend/src/js/entity-history/timeline/YearHead.tsx index d51839a32d..1db4f50751 100644 --- a/frontend/src/js/entity-history/timeline/YearHead.tsx +++ b/frontend/src/js/entity-history/timeline/YearHead.tsx @@ -46,7 +46,7 @@ const YearHead = ({
{year}
- {totalEvents} {t("history.events", { count: totalEvents })} + {totalEvents} {t("history.events", { count: totalEvents })}
From ed95bf5ab28421b0c1fbc1c4a4666f5822cf8812 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Tue, 10 Jan 2023 17:10:41 +0100 Subject: [PATCH 19/86] Save and load ID kind --- frontend/src/js/entity-history/History.tsx | 8 +++--- .../js/entity-history/LoadHistoryDropzone.tsx | 25 ++++++++++------- frontend/src/js/entity-history/Navigation.tsx | 27 +++++++++++-------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/frontend/src/js/entity-history/History.tsx b/frontend/src/js/entity-history/History.tsx index bd502dabd7..37c59653a5 100644 --- a/frontend/src/js/entity-history/History.tsx +++ b/frontend/src/js/entity-history/History.tsx @@ -19,7 +19,7 @@ import type { LoadingPayload } from "./LoadHistoryDropzone"; import { Navigation } from "./Navigation"; import SourcesControl from "./SourcesControl"; import Timeline from "./Timeline"; -import { DEFAULT_ID_KIND, useUpdateHistorySession } from "./actions"; +import { useUpdateHistorySession } from "./actions"; import { EntityId } from "./reducer"; const FullScreen = styled("div")` @@ -145,10 +145,8 @@ export const History = () => { }: LoadingPayload) => { updateHistorySession({ label, - // Here, we're assuming that ids from the uploaded file have a default id kind - // TODO: possibly allow users to specifiy the id kind from the file they're uploading - entityIds: loadedEntityIds.map((id) => ({ id, kind: DEFAULT_ID_KIND })), - entityId: { id: loadedEntityIds[0], kind: DEFAULT_ID_KIND }, + entityIds: loadedEntityIds, + entityId: loadedEntityIds[0], }); setEntityIdsStatus(loadedEntityStatus); setEntityStatusOptions(loadedEntityStatusOptions); diff --git a/frontend/src/js/entity-history/LoadHistoryDropzone.tsx b/frontend/src/js/entity-history/LoadHistoryDropzone.tsx index 8a31f1c9c2..6ccc3c98ed 100644 --- a/frontend/src/js/entity-history/LoadHistoryDropzone.tsx +++ b/frontend/src/js/entity-history/LoadHistoryDropzone.tsx @@ -11,6 +11,7 @@ import DropzoneWithFileInput, { } from "../ui-components/DropzoneWithFileInput"; import type { EntityIdsStatus } from "./History"; +import { EntityId } from "./reducer"; const ImportButtonSpacer = styled("div")` height: 30px; @@ -20,7 +21,7 @@ const acceptedDropTypes = [NativeTypes.FILE]; export interface LoadingPayload { label: string; - loadedEntityIds: string[]; + loadedEntityIds: EntityId[]; loadedEntityStatus: EntityIdsStatus; loadedEntityStatusOptions: SelectOptionT[]; } @@ -46,22 +47,26 @@ export const LoadHistoryDropzone = ({ label: string; data: string[][]; }) => { - const loadedEntityIds = []; + const loadedEntityIds: EntityId[] = []; const loadedEntityStatus: EntityIdsStatus = {}; const loadedEntityStatusOptionsRaw: string[] = []; for (const row of data) { - if (row.length !== 2) { + if (row.length < 2) { continue; } - loadedEntityIds.push(row[0]); - if (row[1]) { - loadedEntityStatus[row[0]] = row[1].split(",").map((s) => { - const opt = s.trim(); - loadedEntityStatusOptionsRaw.push(s); - return { label: opt, value: opt }; - }); + loadedEntityIds.push({ kind: row[0], id: row[1] }); + + if (row.length > 2) { + loadedEntityStatus[row[1]] = row + .slice(2) + .filter((str) => str.length > 0) + .map((s) => { + const opt = s.trim(); + loadedEntityStatusOptionsRaw.push(opt); + return { label: opt, value: opt }; + }); } } diff --git a/frontend/src/js/entity-history/Navigation.tsx b/frontend/src/js/entity-history/Navigation.tsx index d5c0fa7169..142abf5a51 100644 --- a/frontend/src/js/entity-history/Navigation.tsx +++ b/frontend/src/js/entity-history/Navigation.tsx @@ -104,17 +104,22 @@ export const Navigation = memo( }, [entityIds, currentEntityIndex, updateHistorySession]); const onDownload = useCallback(() => { - const idToRow = (entityId: EntityId) => [ - entityId.id, - entityIdsStatus[entityId.id] - ? entityIdsStatus[entityId.id].map((o) => o.value) - : "", - ]; - - const csvString = entityIds - .map(idToRow) - .map((row) => row.join(";")) - .join("\n"); + const usedStatuses = Object.values(entityIdsStatus).reduce( + (longest, el) => (longest.length > el.length ? longest : el), + [], + ); + const idToRow = (entityId: EntityId) => + [ + entityId.kind, + entityId.id, + usedStatuses + .map((opt) => + entityIdsStatus[entityId.id]?.includes(opt) ? opt.value : "", + ) + .join(";"), + ].join(";"); + + const csvString = entityIds.map(idToRow).join("\n"); const blob = new Blob([csvString], { type: "application/csv", From 6dfee5250a26ab85ce8a67856fd759193c318743 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Tue, 10 Jan 2023 17:10:50 +0100 Subject: [PATCH 20/86] Cleanup HoverNavigatable --- .../src/js/small-tab-navigation/HoverNavigatable.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/js/small-tab-navigation/HoverNavigatable.tsx b/frontend/src/js/small-tab-navigation/HoverNavigatable.tsx index 0d2ff66f7a..8e5e11471f 100644 --- a/frontend/src/js/small-tab-navigation/HoverNavigatable.tsx +++ b/frontend/src/js/small-tab-navigation/HoverNavigatable.tsx @@ -27,8 +27,8 @@ export const HoverNavigatable = ({ children, className, }: PropsT) => { - // Type Number is in Browser, however local typescript does not recognize it - let [timeout, setTimeoutVariable] = useState(null); + const [timeoutVar, setTimeoutVar] = useState(null); + const [{ isOver }, drop] = useDrop({ accept: [ DNDType.FORM_CONFIG, @@ -37,10 +37,10 @@ export const HoverNavigatable = ({ DNDType.PREVIOUS_SECONDARY_ID_QUERY, ], hover: (_, monitor) => { - if (timeout == null) { - setTimeoutVariable( + if (timeoutVar == null) { + setTimeoutVar( setTimeout(() => { - setTimeoutVariable(null); + setTimeoutVar(null); if (monitor.isOver()) { triggerNavigate(); } @@ -52,6 +52,7 @@ export const HoverNavigatable = ({ isOver: monitor.isOver(), }), }); + return ( {children} From 30d9400fdcaea52132e3355fdf37c9da8d86a1ba Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Tue, 10 Jan 2023 17:44:19 +0100 Subject: [PATCH 21/86] Add import placeholder and description --- .../js/entity-history/LoadHistoryDropzone.tsx | 5 +++++ .../ui-components/DropzoneWithFileInput.tsx | 9 +++++++- frontend/src/js/ui-components/ImportModal.tsx | 21 ++++++++++++++++++- frontend/src/localization/de.json | 2 ++ frontend/src/localization/en.json | 2 ++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/frontend/src/js/entity-history/LoadHistoryDropzone.tsx b/frontend/src/js/entity-history/LoadHistoryDropzone.tsx index 6ccc3c98ed..4b29b64b85 100644 --- a/frontend/src/js/entity-history/LoadHistoryDropzone.tsx +++ b/frontend/src/js/entity-history/LoadHistoryDropzone.tsx @@ -11,6 +11,7 @@ import DropzoneWithFileInput, { } from "../ui-components/DropzoneWithFileInput"; import type { EntityIdsStatus } from "./History"; +import { DEFAULT_ID_KIND } from "./actions"; import { EntityId } from "./reducer"; const ImportButtonSpacer = styled("div")` @@ -113,6 +114,10 @@ export const LoadHistoryDropzone = ({ disableClick showImportButton onImportLines={onImportLines} + importPlaceholder={t("history.load.importPlaceholder", { + idkind: DEFAULT_ID_KIND, + })} + importDescription={t("history.load.importDescription")} > {() => ( <> diff --git a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx index f7177425a9..9d470d9c0d 100644 --- a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx +++ b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx @@ -57,10 +57,13 @@ interface PropsT { acceptedDropTypes?: string[]; accept?: string; disableClick?: boolean; - showImportButton?: boolean; isInitial?: boolean; className?: string; + + showImportButton?: boolean; onImportLines?: (lines: string[]) => void; + importPlaceholder?: string; + importDescription?: string; } /* @@ -77,6 +80,8 @@ const DropzoneWithFileInput = < { onSelectFile, onImportLines, + importPlaceholder, + importDescription, acceptedDropTypes, disableClick, showImportButton, @@ -135,6 +140,8 @@ const DropzoneWithFileInput = < setImportModalOpen(false)} onSubmit={onSubmitImport} + placeholder={importPlaceholder} + description={importDescription} /> )} {showImportButton && onImportLines && ( diff --git a/frontend/src/js/ui-components/ImportModal.tsx b/frontend/src/js/ui-components/ImportModal.tsx index fbd4be8e0f..7b9810f4e2 100644 --- a/frontend/src/js/ui-components/ImportModal.tsx +++ b/frontend/src/js/ui-components/ImportModal.tsx @@ -32,6 +32,11 @@ const HiddenFileInput = styled("input")` display: none; `; +const Subtitle = styled(`p`)` + margin: 0; + max-width: 600px; +`; + const acceptedDropTypes = [NativeTypes.FILE]; const useCanReadClipboard = () => { @@ -54,9 +59,13 @@ const useCanReadClipboard = () => { }; export const ImportModal = ({ + placeholder, + description, onClose, onSubmit, }: { + description?: string; + placeholder?: string; onClose: () => void; onSubmit: (lines: string[]) => void; }) => { @@ -138,13 +147,23 @@ export const ImportModal = ({ onClose={onClose} > + {description && ( + + )} - {() =>