From d12f2a2057cc368d94bd0b6e30539fce65d87693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Wed, 16 Oct 2024 14:48:56 +0200 Subject: [PATCH 1/4] feat: order professional translation --- .../TranslationAgencyModel.kt | 16 + .../main/kotlin/io/tolgee/model/task/Task.kt | 4 + .../translationAgency/TranslationAgency.kt | 33 + .../kotlin/io/tolgee/service/AvatarService.kt | 12 +- .../organization/OrganizationService.kt | 3 +- .../main/resources/db/changelog/schema.xml | 3087 +++++++++-------- e2e/cypress/support/dataCyType.d.ts | 7 + .../CreateTranslationAgencyRequest.kt | 15 + .../UpdateTranslationAgencyRequest.kt | 15 + .../component/common/avatar/AvatarEdit.tsx | 15 +- .../common/avatar/AvatarEditDialog.tsx | 9 +- .../component/common/avatar/ProfileAvatar.tsx | 10 +- webapp/src/constants/links.tsx | 16 + .../AdministrationCloudPlanCreateView.tsx | 0 .../AdministrationCloudPlanEditView.tsx | 0 .../AdministrationCloudPlansView.tsx | 0 .../AdministrationEeLicenseView.tsx | 6 +- .../AdministrationEePlanCreateView.tsx | 0 .../AdministrationEePlanEditView.tsx | 0 .../AdministrationEePlansView.tsx | 0 .../components/AssignSwitchCheckbox.tsx | 0 .../components/CloudPlanForm.tsx | 0 .../components/CloudPlanOrganizations.tsx | 0 .../components/EePlanForm.tsx | 0 .../components/EePlanOrganizations.tsx | 0 .../AdministrationEeTACreateView.tsx | 55 + .../AdministrationEeTAEditView.tsx | 96 + .../AdministrationEeTAView.tsx | 108 + .../translationAgencies/TAEditForm.tsx | 111 + .../translationAgencies/TAProfileAvatar.tsx | 84 + .../OrderTranslationsDialog.tsx | 333 ++ .../orderTranslations/ProviderDescription.tsx | 28 + .../orderTranslations/TranslationAgency.tsx | 94 + webapp/src/ee/task/components/TasksBoard.tsx | 21 +- .../taskCreate/TaskCreateDialog.tsx | 190 +- .../components/taskCreate/TaskCreateForm.tsx | 216 ++ .../components/taskCreate/TaskPreview.tsx | 46 +- .../components/tasksHeader/TasksHeaderBig.tsx | 16 +- .../src/ee/task/views/myTasks/MyTasksList.tsx | 12 + .../src/ee/task/views/myTasks/MyTasksView.tsx | 60 +- .../{TasksList.tsx => ProjectTasksList.tsx} | 14 +- .../views/projectTasks/ProjectTasksView.tsx | 151 +- webapp/src/fixtures/isValidUrl.ts | 8 + webapp/src/service/apiSchema.generated.ts | 1003 ++++-- .../src/service/billingApiSchema.generated.ts | 443 ++- .../administration/AdministrationView.tsx | 26 +- .../components/BaseAdministrationView.tsx | 4 + 47 files changed, 4328 insertions(+), 2039 deletions(-) create mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/translationAgency/TranslationAgencyModel.kt create mode 100644 backend/data/src/main/kotlin/io/tolgee/model/translationAgency/TranslationAgency.kt create mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/CreateTranslationAgencyRequest.kt create mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/UpdateTranslationAgencyRequest.kt rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/AdministrationCloudPlanCreateView.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/AdministrationCloudPlanEditView.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/AdministrationCloudPlansView.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/AdministrationEeLicenseView.tsx (87%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/AdministrationEePlanCreateView.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/AdministrationEePlanEditView.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/AdministrationEePlansView.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/components/AssignSwitchCheckbox.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/components/CloudPlanForm.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/components/CloudPlanOrganizations.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/components/EePlanForm.tsx (100%) rename webapp/src/ee/billing/administration/{ => subscriptionPlans}/components/EePlanOrganizations.tsx (100%) create mode 100644 webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx create mode 100644 webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTAEditView.tsx create mode 100644 webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTAView.tsx create mode 100644 webapp/src/ee/billing/administration/translationAgencies/TAEditForm.tsx create mode 100644 webapp/src/ee/billing/administration/translationAgencies/TAProfileAvatar.tsx create mode 100644 webapp/src/ee/orderTranslations/OrderTranslationsDialog.tsx create mode 100644 webapp/src/ee/orderTranslations/ProviderDescription.tsx create mode 100644 webapp/src/ee/orderTranslations/TranslationAgency.tsx create mode 100644 webapp/src/ee/task/components/taskCreate/TaskCreateForm.tsx rename webapp/src/ee/task/views/projectTasks/{TasksList.tsx => ProjectTasksList.tsx} (83%) create mode 100644 webapp/src/fixtures/isValidUrl.ts diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/translationAgency/TranslationAgencyModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/translationAgency/TranslationAgencyModel.kt new file mode 100644 index 0000000000..564461f9cc --- /dev/null +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/translationAgency/TranslationAgencyModel.kt @@ -0,0 +1,16 @@ +package io.tolgee.hateoas.translationAgency + +import io.tolgee.dtos.Avatar +import io.tolgee.hateoas.task.TaskModel +import org.springframework.hateoas.RepresentationModel +import org.springframework.hateoas.server.core.Relation + +@Relation(collectionRelation = "translationAgencies", itemRelation = "translationAgency") +class TranslationAgencyModel( + var id: Long = 0L, + var name: String = "", + var description: String? = "", + var services: List = listOf(), + var url: String? = "", + val avatar: Avatar?, +) : RepresentationModel() diff --git a/backend/data/src/main/kotlin/io/tolgee/model/task/Task.kt b/backend/data/src/main/kotlin/io/tolgee/model/task/Task.kt index f958388ac7..0ac1246d05 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/task/Task.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/task/Task.kt @@ -11,6 +11,7 @@ import io.tolgee.model.StandardAuditModel import io.tolgee.model.UserAccount import io.tolgee.model.enums.TaskState import io.tolgee.model.enums.TaskType +import io.tolgee.model.translationAgency.TranslationAgency import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType @@ -87,4 +88,7 @@ class Task : StandardAuditModel() { @ActivityLoggedProp var closedAt: Date? = null + + @ManyToOne(fetch = FetchType.LAZY, optional = true) + var agency: TranslationAgency? = null } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/translationAgency/TranslationAgency.kt b/backend/data/src/main/kotlin/io/tolgee/model/translationAgency/TranslationAgency.kt new file mode 100644 index 0000000000..25395c21e8 --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/model/translationAgency/TranslationAgency.kt @@ -0,0 +1,33 @@ +package io.tolgee.model.translationAgency + +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType +import io.tolgee.model.EntityWithId +import io.tolgee.model.ModelWithAvatar +import io.tolgee.model.StandardAuditModel +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Table +import jakarta.validation.constraints.Size +import org.hibernate.annotations.Type + +@Entity +@Table() +class TranslationAgency : StandardAuditModel(), ModelWithAvatar, EntityWithId { + @field:Size(max = 255) + @Column(length = 255) + var name: String = "" + + @field:Size(max = 2000) + @Column(length = 2000) + var description: String? = null + + @Type(JsonBinaryType::class) + @Column(columnDefinition = "jsonb") + var services: MutableList = mutableListOf() + + @field:Size(max = 255) + @Column(length = 255) + var url: String? = null + + override var avatarHash: String? = null +} diff --git a/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt b/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt index fce4889cfa..c78b33889c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt @@ -29,11 +29,15 @@ class AvatarService( fun storeAvatarFiles( avatar: InputStream, entity: ModelWithAvatar, + dimension: Dimension? = null, + thumbnailSize: Int? = null, ): String { + val dim = dimension ?: Dimension(200, 200) + val thumbSize = thumbnailSize ?: 50 val avatarBytes = avatar.readAllBytes() val converter = ImageConverter(avatarBytes.inputStream()) - val large = converter.getImage(-1f, Dimension(200, 200)).toByteArray() - val thumb = converter.getThumbnail(50).toByteArray() + val large = converter.getImage(-1f, dim).toByteArray() + val thumb = converter.getThumbnail(thumbSize).toByteArray() val idByteArray = "${entity::class.simpleName}-${entity.id}---".toByteArray() val bytesToHash = idByteArray + large val hashBinary = MessageDigest.getInstance("SHA-256").digest(bytesToHash) @@ -48,8 +52,10 @@ class AvatarService( fun setAvatar( entity: ModelWithAvatar, avatar: InputStream, + dimension: Dimension? = null, + thumbnailSize: Int? = null, ) { - val hash = storeAvatarFiles(avatar, entity) + val hash = storeAvatarFiles(avatar, entity, dimension, thumbnailSize) removeAvatar(entity) entity.avatarHash = hash } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt index 0f5dbd0eb0..d32a602372 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt @@ -39,6 +39,7 @@ import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.awt.Dimension import java.io.InputStream import io.tolgee.dtos.cacheable.OrganizationDto as CachedOrganizationDto @@ -301,7 +302,7 @@ class OrganizationService( organization: Organization, avatar: InputStream, ) { - avatarService.setAvatar(organization, avatar) + avatarService.setAvatar(organization, avatar, Dimension(300, 60)) } /** diff --git a/backend/data/src/main/resources/db/changelog/schema.xml b/backend/data/src/main/resources/db/changelog/schema.xml index 6f57803241..a0e277cf99 100644 --- a/backend/data/src/main/resources/db/changelog/schema.xml +++ b/backend/data/src/main/resources/db/changelog/schema.xml @@ -1,3881 +1,4060 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd"> - + - + - + - - - - + + + + - + - + - + - + - + - + - + - - - + + + - + - + - + - - - - + + + + - + - + - + - - - + + + - + - + - + - - + + - + - + - + - - - + + + - + - + - + - - - - - - - + + + + + + + - + - + - + - + - + - + - + + constraintName="useraccount_authtype_auth_id" tableName="user_account" /> - + - + - - + + + constraintName="FK1yntjh7ggi4te3ovft3q98vjh" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="invitation" + validate="true" /> + constraintName="FK4eao8empmjkh4djn65mgpo0xy" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="repository" + validate="true" /> + constraintName="FK4w7p5ffylu2s25r0fvcg7m16m" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="repository" + validate="true" /> + constraintName="FKan8l57bdrd3yfsra16gy812nh" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="source" + validate="true" /> + constraintName="FKc2d46w0v0yhqdf6nyel7g0b3u" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="language" + validate="true" /> + constraintName="FKd4loxn3vchrap5ulsueynd7m2" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKk3nnt1okbdkvp7igkexvbyxnf" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="repository" + validate="true" /> + constraintName="FKl6qxgkp6jih1aup8laua5ime9" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="repository" + validate="true" /> + constraintName="FKoiov5289pveggjm2sthuhw4qm" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKsryptvc972nlvw7we7fkr06mg" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> - + - + - + - + - - + + - + - + - + - + + constraintName="FKojcltkbo4pxlf41nwlxwkgtjy" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="key" + validate="true" /> - + - + - + - + - + - + - + - + - + + constraintName="FKspqbeiygfq5lk5dw2sgt9pbml" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="api_key" + validate="true" /> - + - - insert into API_KEY_SCOPES_ENUM - select id, 0 - from API_KEY - where SCOPES like '%translations.view%'; - insert into API_KEY_SCOPES_ENUM - select id, 1 - from API_KEY - where SCOPES like '%translations.edit%'; - insert into API_KEY_SCOPES_ENUM - select id, 2 - from API_KEY - where SCOPES like '%keys.edit%'; - - - delete from API_KEY_SCOPES_ENUM where 1 = 1 - + insert into API_KEY_SCOPES_ENUM select id, 0 from API_KEY where SCOPES like + '%translations.view%'; insert into API_KEY_SCOPES_ENUM select id, 1 from API_KEY where + SCOPES like '%translations.edit%'; insert into API_KEY_SCOPES_ENUM select id, 2 from + API_KEY where SCOPES like '%keys.edit%'; + delete from API_KEY_SCOPES_ENUM where 1 = 1 - + - - - - + + + + - + - + - + - - - - + + + + - + - + - + + constraintName="FK581ryvy4rn9ey7rx3lestkuix" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="invitation" + validate="true" /> + constraintName="FK6p6nbxihffjckr4tayum4x67v" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKqj2a0uojwh4p5rc9vfsu92fdk" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="organization" + validate="true" /> - + - + - + - + + constraintName="FKe8q5ar4olfji1mw1p0163ctyk" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKp2q5w860n1uai6oyel74udi83" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="organization" + validate="true" /> + constraintName="organization_role_user_organization_unique" + tableName="organization_role" /> - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - - - + + + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + + constraintName="FK2q8pryy6fohmtjm1hmu7tw63s" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_file" + validate="true" /> + constraintName="FK2twe5erdt7yfludaoiq7gv3mf" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FK5yemkngk70scn7l0r4cwlmtn" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_file" + validate="true" /> + constraintName="FK8hvxh3e4cxn5561l76xejytnx" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import" + validate="true" /> + constraintName="FKapx5aa95fl74isypara87i0oe" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="key_meta" + validate="true" /> + constraintName="FKcrettb2iyas70c96411bncjb7" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="key_meta" + validate="true" /> + constraintName="FKfm8n337a6jyvume29k80edgvj" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" + referencedTableName="import_language" validate="true" /> + constraintName="FKgdb4y0up36tyubhysfpbru11b" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="key" + validate="true" /> + constraintName="FKikmqn4hh8e4fp8k4dj16fkh3y" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_key" + validate="true" /> + constraintName="FKjawljy0kpt4o2j8ix7qv1x196" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_file" + validate="true" /> + constraintName="FKlry3bmimp7a094jj0kioe6ga5" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" + referencedTableName="import_file_issue" validate="true" /> + constraintName="FKnhtysiwn7w2tn04nr4smmc6af" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKnus5ewftf9lxhm520whg3msnh" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKp7xnyjio5do5p6yb8ch2hrudw" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_file" + validate="true" /> - + + constraintName="FKqpyb4896knd8m41ffn1f7wgeo" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_key" + validate="true" /> + constraintName="FKr3w7b3sw9aiegenlgjjnjt2yi" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="repository" + validate="true" /> + constraintName="FKrlpc5t6f2d5v6w7r1jsk7y01l" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="translation" + validate="true" /> + constraintName="FKrtx4aq0bqg6m83cdbi8vcuyve" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_key" + validate="true" /> - + + tableName="api_key" + newColumnName="project_id" + oldColumnName="repository_id" /> + tableName="import" + newColumnName="project_id" + oldColumnName="repository_id" /> + tableName="key" + newColumnName="project_id" + oldColumnName="repository_id" /> + tableName="language" + newColumnName="project_id" + oldColumnName="repository_id" /> + tableName="permission" + newColumnName="project_id" + oldColumnName="repository_id" /> - + - + + tableName="language" /> + tableName="project" /> - - + + + constraintName="FK1egspeawij8o52ckn75n0di8u" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> + constraintName="FKb0ubhe0qiqd6er95wwhguincu" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> + constraintName="FKboemy1qcry5x32aoimycf6jrt" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="organization" + validate="true" /> + constraintName="FKe30ekt7igqooxef59tbedyfdj" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> + constraintName="FKg12n5rjbnfoqlwpdkv3tql81p" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> + constraintName="FKno8mwmeoh2j8hv6yb6ghrepew" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKoknj6iem9tqstdembw2rrvvs1" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + constraintName="FKe87b2cgwpk3qttqh95tgrehlt" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="language" + validate="true" /> - + - + - + - + - + - - - - - + + + + + - + - + - + - + - + - + - - - - + + + + - + - + - - + + - + - + - - - + + + - + - + - - - - - - + + + + + + - + - + - - - - - + + + + + - + - + - - - - - + + + + + - + - + - - - - - + + + + + - + - + - - - - - - - + + + + + + + - + - + - + - + - + - - + + - + - + - - - - - + + + + + - + - + - + - - - - - + + + + + - + - + - - - - - - + + + + + + - + - + - - - - - - - - + + + + + + + + + constraintName="FK1diai27pxl7epr7wmiddlwns5" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FK3jkyqktbidh2cyx3xy3qml99l" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FK9ac0dmcy0armtdh781gs8bq01" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKb7qk3ljn3o8bqwum6a5io3tpe" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKcwyby3t6jg4ybxox3nchfbpqy" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKdkc05rqm5p3ahqp80ukr4y6se" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKfi9ywuk505xl9g17jib6b7urx" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> - + + constraintName="FKgey5u1uwkslc6hhyp3uka2wj8" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKgrkrhev0nmxi9r1h096cm8lxe" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKhqt2jx03076gcmame7hggtppa" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKi84l90hnwf1m1wg1hn2ov7ykr" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKlkb2y39ix1nhtnjkco5pkwa5w" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKlkd3khuueqm30qwpdrku4w6eo" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKq4t05s4qw8uasc9mjl2tk83sm" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKq6jhbxmwvr0o19sfqwog5gk7s" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKq8g6phhnoyyuyjh9omekg8ja7" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> + constraintName="FKqsyp928iixx9oharj1ofavbk5" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> - + - + - + - + - + - + - - + + + constraintName="FK1hhq8sx3mn7isckcoaq277ix6" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="tag" + validate="true" /> + constraintName="FKbyy56vice9njgl86752up8120" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> + constraintName="FKsbnhi8xkxfybrf26ypraemrmk" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="key_meta" + validate="true" /> - + - + - + - + - + - + - + - + - + - + - + - + - - + + + tableName="uploaded_image" /> + constraintName="FKelfpas8y8t5pq40m57ttdcnf" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> - + - + + constraintName="organization_third_party_billing_id_unique" tableName="organization" /> + columnName="name" + newDataType="varchar(2000)" + tableName="key" /> - + - + - + - + - - - + + + - + - + - + - - - + + + - + - + - + + constraintName="FK7cklk7byifpo0jj5q58dwn6rt" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> + constraintName="FKlgwarx2xdphg9imptapjv2p6j" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> + constraintName="FKsx03b0rw40yo8jtqkklir7h04" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="organization" + validate="true" /> + baseTableName="mt_service_config_enabled_services" + constraintName="FKvo4mgub2uvhr99nqjm2gwy57" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" + referencedTableName="mt_service_config" validate="true" /> - - CREATE - EXTENSION IF NOT EXISTS pg_trgm; - CREATE INDEX translation_text_gin_trgm_index ON translation USING GIN (text gin_trgm_ops); - + CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE INDEX + translation_text_gin_trgm_index ON translation USING GIN (text gin_trgm_ops); - + - + - + - + - + - + - + - + - + - + - + - + + constraintName="FK209w4e9w8xrj3grtabgpuolnr" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="language" + validate="true" /> - + + constraintName="FKqrbr6c2d8ujo1y43a6ftsdp8c" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="revision" + validate="true" /> - + - + - + - + - - UPDATE translation - set state = 1 - where state = 2 - or state = 4; - UPDATE translation - set state = 2 - where state = 3; - + UPDATE translation set state = 1 where state = 2 or state = 4; + UPDATE translation set state = 2 where state = 3; - + - + - + - + - + - + - + - + - + - + - + - - UPDATE translation_aud - set state = 1 - where state = 2 - or state = 4; - UPDATE translation_aud - set state = 2 - where state = 3; - + UPDATE translation_aud set state = 1 where state = 2 or state = 4; + UPDATE translation_aud set state = 2 where state = 3; - + - + - + - - - + + + - + - + - + - - - - + + + + - + - + - - - + + + - + - + + constraintName="activity_describing_entityPK" tableName="activity_describing_entity" /> + constraintName="activity_modified_entityPK" tableName="activity_modified_entity" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + constraintName="FKqrbr6c2d8ujo1y43a6ftsdp8c" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + constraintName="FKdhlxchq1m65xxjrioelwhqrh7" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + constraintName="FKh2vk27p76ib735etoyfl2x0p" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="language" + validate="true" /> - + - + - + - + - + - + - + - - - - - + + + + + - + + constraintName="FKdjjacqlrm6ps628vl2tgv6af9" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="user_account" + validate="true" /> - + - + - + - + - + - + - + - + - + - - UPDATE user_account - SET account_type = 'LOCAL'; - UPDATE user_account - SET account_type = 'THIRD_PARTY' - WHERE password IS NULL; - UPDATE user_account - SET account_type = 'LDAP' - WHERE password IS NULL - AND third_party_auth_type IS NULL; - + UPDATE user_account SET account_type = 'LOCAL'; UPDATE user_account + SET account_type = 'THIRD_PARTY' WHERE password IS NULL; UPDATE user_account SET + account_type = 'LDAP' WHERE password IS NULL AND third_party_auth_type IS NULL; - + - + - + - + - - create unique index on public.user_account (username) where deleted_at is null; - + create unique index on public.user_account (username) where deleted_at is null; - - create unique index on public.user_account (third_party_auth_id, third_party_auth_type) where deleted_at is null; - + create unique index on public.user_account (third_party_auth_id, + third_party_auth_type) where deleted_at is null; - + - + - + - + - - + + - + + constraintName="FK7ex52jk5v6xtvpbaojlmv9cas" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="namespace" + validate="true" /> + constraintName="FKo9xwhbk7m02ohl0qnxk658hud" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="project" + validate="true" /> - + - - create unique index on public.key (project_id, "name", namespace_id) where namespace_id is not null; - - - create unique index on public.key (project_id, "name") where namespace_id is null; - + create unique index on public.key (project_id, "name", namespace_id) where + namespace_id is not null; + create unique index on public.key (project_id, "name") where namespace_id is null; - + + tableName="namespace" /> - + + constraintName="FK2lyqy9kydj78d72iatgt0c509" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="import_file" + validate="true" /> - + - + - + - + - + - + ANY - - CREATE - EXTENSION IF NOT EXISTS unaccent; - CREATE - EXTENSION IF NOT EXISTS pg_trgm; - - CREATE - OR REPLACE FUNCTION public.f_unaccent(text) - RETURNS text - LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS - $func$ - SELECT public.unaccent('public.unaccent', $1) - $func$; - - CREATE INDEX translation_text_unaccent_gin_trgm_index ON translation USING GIN ( lower (f_unaccent(text)) gin_trgm_ops); - CREATE INDEX key_name_unaccent_gin_trgm_index ON key USING GIN (lower (f_unaccent(name)) gin_trgm_ops); - CREATE INDEX namespace_name_unaccent_gin_trgm_index ON namespace USING GIN (lower (f_unaccent(name)) gin_trgm_ops); - + CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT + EXISTS pg_trgm; CREATE OR REPLACE FUNCTION public.f_unaccent(text) RETURNS text LANGUAGE + sql IMMUTABLE PARALLEL SAFE STRICT AS $func$ SELECT public.unaccent('public.unaccent', + $1) $func$; CREATE INDEX translation_text_unaccent_gin_trgm_index ON translation USING + GIN ( lower (f_unaccent(text)) gin_trgm_ops); CREATE INDEX + key_name_unaccent_gin_trgm_index ON key USING GIN (lower (f_unaccent(name)) + gin_trgm_ops); CREATE INDEX namespace_name_unaccent_gin_trgm_index ON namespace USING + GIN (lower (f_unaccent(name)) gin_trgm_ops); - - + + - + - + - + - + + constraintName="FKp6wnbfbgoxnqv6wihs7m63v5t" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="key" + validate="true" /> - - update screenshot - set path = k.project_id || '/' || k.id from key k - where k.id = screenshot.key_id; - - insert into key_screenshot_reference(key_id, screenshot_id) - select s.key_id, s.id - from screenshot s - join key k on k.id = s.key_id; - + update screenshot set path = k.project_id || '/' || k.id from key k + where k.id = screenshot.key_id; insert into key_screenshot_reference(key_id, + screenshot_id) select s.key_id, s.id from screenshot s join key k on k.id = s.key_id; - + - + - + - + - + - + - + - + - + - + - + - + - - ALTER TABLE key_screenshot_reference ALTER COLUMN original_text TYPE text; - + ALTER TABLE key_screenshot_reference ALTER COLUMN original_text TYPE text; - + - + ANY - - create unique index permission_null_idx on permission (organization_id); - insert into permission (id, type, organization_id, created_at, updated_at) - select nextval('permission_id_seq'), base_permissions, id, current_date, current_date - from organization; - + create unique index permission_null_idx on permission (organization_id); insert into + permission (id, type, organization_id, created_at, updated_at) select + nextval('permission_id_seq'), base_permissions, id, current_date, current_date from + organization; - + + constraintName="FKodqlqlhjgsfwnq418388o4vy7" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="organization" + validate="true" /> - + - + - + - + - + - + - + - + + baseTableName="permission_state_change_languages" + constraintName="FKpxsgkx94rrmdmqocvliirtevo" deferrable="false" + initiallyDeferred="false" referencedColumnNames="id" referencedTableName="language" + validate="true" /> - - alter table api_key_scopes_enum alter column scopes_enum type varchar(255) - using case - when scopes_enum = 0 then 'TRANSLATIONS_VIEW' - when scopes_enum = 1 then 'TRANSLATIONS_EDIT' - when scopes_enum = 2 then 'KEYS_EDIT' - when scopes_enum = 3 then 'SCREENSHOTS_UPLOAD' - when scopes_enum = 4 then 'SCREENSHOTS_DELETE' - when scopes_enum = 5 then 'SCREENSHOTS_VIEW' - when scopes_enum = 6 then 'ACTIVITY_VIEW' - when scopes_enum = 7 then 'LANGUAGES_EDIT' - end; - + alter table api_key_scopes_enum alter column scopes_enum type varchar(255) using case + when scopes_enum = 0 then 'TRANSLATIONS_VIEW' when scopes_enum = 1 then + 'TRANSLATIONS_EDIT' when scopes_enum = 2 then 'KEYS_EDIT' when scopes_enum = 3 then + 'SCREENSHOTS_UPLOAD' when scopes_enum = 4 then 'SCREENSHOTS_DELETE' when scopes_enum = 5 + then 'SCREENSHOTS_VIEW' when scopes_enum = 6 then 'ACTIVITY_VIEW' when scopes_enum = 7 + then 'LANGUAGES_EDIT' end; - - insert into api_key_scopes_enum (api_key_id, scopes_enum) - select api_key_id, 'KEYS_CREATE' - from api_key_scopes_enum - where scopes_enum = 'KEYS_EDIT'; - insert into api_key_scopes_enum (api_key_id, scopes_enum) - select api_key_id, 'KEYS_DELETE' - from api_key_scopes_enum - where scopes_enum = 'KEYS_EDIT'; - + insert into api_key_scopes_enum (api_key_id, scopes_enum) select api_key_id, + 'KEYS_CREATE' from api_key_scopes_enum where scopes_enum = 'KEYS_EDIT'; insert into + api_key_scopes_enum (api_key_id, scopes_enum) select api_key_id, 'KEYS_DELETE' from + api_key_scopes_enum where scopes_enum = 'KEYS_EDIT'; - + - + - + + tableName="translation" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - + + + - + - + - - - + + + - + - + - + - + - - - + + + - + - - + + - + - + - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - UPDATE user_account - SET is_initial_user = true - WHERE id = ( - SELECT id FROM user_account WHERE username != '___implicit_user' ORDER BY id LIMIT 1 - ); - + UPDATE user_account SET is_initial_user = true WHERE id = ( SELECT + id FROM user_account WHERE username != '___implicit_user' ORDER BY id LIMIT 1 ); - - UPDATE user_account - SET account_type = 'MANAGED' - WHERE account_type = 'LDAP'; - + UPDATE user_account SET account_type = 'MANAGED' WHERE account_type + = 'LDAP'; - + - + - + - + - + - + - + - + - - - - + + + + - + - + - + - - - - + + + + - + - - + + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - + - + - - - + + + - + - - - - - + + + + + - + - + - + - - - - - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - SELECT CASE - WHEN EXISTS (SELECT 1 - FROM information_schema.columns - WHERE table_name = 'batch_job_execution_params' - AND column_name = 'parameter_value') - THEN 'true' - ELSE 'false' - END; - - - SELECT CASE - WHEN EXISTS (SELECT 1 - FROM information_schema.columns - WHERE table_name = 'batch_job_execution_params') - THEN 'true' - ELSE 'false' - END; - + SELECT CASE WHEN EXISTS (SELECT 1 FROM + information_schema.columns WHERE table_name = 'batch_job_execution_params' AND + column_name = 'parameter_value') THEN 'true' ELSE 'false' END; + SELECT CASE WHEN EXISTS (SELECT 1 FROM + information_schema.columns WHERE table_name = 'batch_job_execution_params') THEN + 'true' ELSE 'false' END; - - ALTER TABLE BATCH_STEP_EXECUTION ADD CREATE_TIME TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00'; - - ALTER TABLE BATCH_STEP_EXECUTION ALTER COLUMN START_TIME DROP NOT NULL; - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN DATE_VAL; - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN LONG_VAL; - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN DOUBLE_VAL; - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN TYPE_CD TYPE VARCHAR(100); - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS RENAME TYPE_CD TO PARAMETER_TYPE; - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN KEY_NAME TYPE VARCHAR(100); - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS RENAME KEY_NAME TO PARAMETER_NAME; - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN STRING_VAL TYPE VARCHAR(2500); - - ALTER TABLE BATCH_JOB_EXECUTION_PARAMS RENAME STRING_VAL TO PARAMETER_VALUE; - + ALTER TABLE BATCH_STEP_EXECUTION ADD CREATE_TIME TIMESTAMP NOT NULL DEFAULT + '1970-01-01 00:00:00'; ALTER TABLE BATCH_STEP_EXECUTION ALTER COLUMN START_TIME DROP NOT + NULL; ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN DATE_VAL; ALTER TABLE + BATCH_JOB_EXECUTION_PARAMS DROP COLUMN LONG_VAL; ALTER TABLE BATCH_JOB_EXECUTION_PARAMS + DROP COLUMN DOUBLE_VAL; ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN TYPE_CD TYPE + VARCHAR(100); ALTER TABLE BATCH_JOB_EXECUTION_PARAMS RENAME TYPE_CD TO PARAMETER_TYPE; + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN KEY_NAME TYPE VARCHAR(100); ALTER + TABLE BATCH_JOB_EXECUTION_PARAMS RENAME KEY_NAME TO PARAMETER_NAME; ALTER TABLE + BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN STRING_VAL TYPE VARCHAR(2500); ALTER TABLE + BATCH_JOB_EXECUTION_PARAMS RENAME STRING_VAL TO PARAMETER_VALUE; - + - + - - CREATE INDEX project_deleted_at_null ON project((deleted_at IS NULL)); - CREATE INDEX organization_deleted_at_null ON organization((deleted_at IS NULL)); - + CREATE INDEX project_deleted_at_null ON project((deleted_at IS NULL)); CREATE INDEX + organization_deleted_at_null ON organization((deleted_at IS NULL)); - + - - CREATE INDEX import_deleted_at_null ON import((deleted_at IS NULL)); - + CREATE INDEX import_deleted_at_null ON import((deleted_at IS NULL)); - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - create unique index import_author_project_unique on import(author_id, project_id) where deleted_at is null - + + create unique index import_author_project_unique on import(author_id, project_id) + where deleted_at is null - + - + - - CREATE INDEX key_description_gin_trgm_index ON key_meta USING GIN (lower (f_unaccent(description)) gin_trgm_ops); - + CREATE INDEX key_description_gin_trgm_index ON key_meta USING GIN (lower + (f_unaccent(description)) gin_trgm_ops); - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - create unique index content_delivery_config_unique_slug on content_delivery_config(slug) where content_storage_id is null; - + + create unique index content_delivery_config_unique_slug on + content_delivery_config(slug) where content_storage_id is null; - + - + - + - + - + - - create index translation_comment_translation_id_state on translation_comment(translation_id, state); - + create index translation_comment_translation_id_state on + translation_comment(translation_id, state); - + - - CREATE OR REPLACE FUNCTION empty_json(j jsonb) RETURNS boolean AS ' - BEGIN - RETURN j = ''{}''::jsonb; - END; - ' LANGUAGE plpgsql IMMUTABLE; - - - CREATE INDEX empty_modifications ON activity_modified_entity (empty_json(modifications)); - + CREATE OR REPLACE FUNCTION empty_json(j jsonb) RETURNS boolean AS ' BEGIN RETURN j = + ''{}''::jsonb; END; ' LANGUAGE plpgsql IMMUTABLE; + CREATE INDEX empty_modifications ON activity_modified_entity + (empty_json(modifications)); - + - + - + - - - - - + + + + + - + - + - + - + - + - - - + + + - + - + - + - - - - - - - + + + + + + + - + - + - + - - - + + + - + - + - + - - - - + + + + - + - + - + - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - create unique index language_project_name on language (project_id, name) where deleted_at is null; - create unique index language_tag_name on language (project_id, tag) where deleted_at is null; - + + + create unique index language_project_name on language (project_id, name) where + deleted_at is null; create unique index language_tag_name on language (project_id, tag) + where deleted_at is null; - + - + - + - + - + - - - - + + + + - + - - - - - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - create index if not exists import_translation_language_id_id on import_translation (language_id); - + create index if not exists import_translation_language_id_id on import_translation + (language_id); - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/cypress/support/dataCyType.d.ts b/e2e/cypress/support/dataCyType.d.ts index c6253a5bd5..4e06b5eb9b 100644 --- a/e2e/cypress/support/dataCyType.d.ts +++ b/e2e/cypress/support/dataCyType.d.ts @@ -48,6 +48,10 @@ declare namespace DataCy { "administration-ee-plans-item-delete" | "administration-ee-plans-item-edit" | "administration-ee-plans-item-public-badge" | + "administration-ee-translation-agencies-field-description" | + "administration-ee-translation-agencies-field-name" | + "administration-ee-translation-agencies-field-services" | + "administration-ee-translation-agencies-field-url" | "administration-frame" | "administration-organizations-list-item" | "administration-organizations-projects-button" | @@ -327,6 +331,8 @@ declare namespace DataCy { "namespaces-select-text-field" | "namespaces-selector" | "navigation-item" | + "order-translation-next" | + "order-translation-submit" | "organization-address-part-field" | "organization-description-field" | "organization-invitation-cancel-button" | @@ -520,6 +526,7 @@ declare namespace DataCy { "tasks-filter-menu" | "tasks-header-add-task" | "tasks-header-filter-select" | + "tasks-header-order-translation" | "tasks-header-show-closed" | "tasks-view-board-button" | "tasks-view-list-button" | diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/CreateTranslationAgencyRequest.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/CreateTranslationAgencyRequest.kt new file mode 100644 index 0000000000..2c95f419b9 --- /dev/null +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/CreateTranslationAgencyRequest.kt @@ -0,0 +1,15 @@ +package io.tolgee.ee.data.translationAgency + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + +data class CreateTranslationAgencyRequest( + @field:NotBlank + @field:Size(min = 3, max = 255) + var name: String = "", + @field:Size(min = 0, max = 2000) + var description: String = "", + var services: List = emptyList(), + @field:Size(min = 0, max = 255) + var url: String = "", +) diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/UpdateTranslationAgencyRequest.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/UpdateTranslationAgencyRequest.kt new file mode 100644 index 0000000000..fda38932dd --- /dev/null +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/translationAgency/UpdateTranslationAgencyRequest.kt @@ -0,0 +1,15 @@ +package io.tolgee.ee.data.translationAgency + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + +data class UpdateTranslationAgencyRequest( + @field:NotBlank + @field:Size(min = 3, max = 255) + var name: String = "", + @field:Size(min = 0, max = 2000) + var description: String = "", + var services: List = emptyList(), + @field:Size(min = 0, max = 255) + var url: String = "", +) diff --git a/webapp/src/component/common/avatar/AvatarEdit.tsx b/webapp/src/component/common/avatar/AvatarEdit.tsx index abf81d5a5d..82d69a3b53 100644 --- a/webapp/src/component/common/avatar/AvatarEdit.tsx +++ b/webapp/src/component/common/avatar/AvatarEdit.tsx @@ -1,20 +1,25 @@ import React, { FC } from 'react'; -import Cropper, { ReactCropperElement } from 'react-cropper'; +import Cropper, { ReactCropperElement, ReactCropperProps } from 'react-cropper'; import { styled } from '@mui/material'; import Box from '@mui/material/Box'; import 'cropperjs/dist/cropper.css'; +import clsx from 'clsx'; const StyledBox = styled(Box)` - .cropper-crop-box, - .cropper-view-box { + &.rounded .cropper-crop-box, + &.rounded .cropper-view-box { border-radius: 50%; } `; +export type CropperOptions = ReactCropperProps & { rounded: boolean }; + export const AvatarEdit: FC<{ src: string; cropperRef: React.RefObject; -}> = ({ src, cropperRef }) => { + cropperProps?: Partial; +}> = ({ src, cropperRef, cropperProps }) => { + const { rounded, ...other } = { ...cropperProps, rounded: true }; return ( ); diff --git a/webapp/src/component/common/avatar/AvatarEditDialog.tsx b/webapp/src/component/common/avatar/AvatarEditDialog.tsx index 2e1a9ae6ad..8ab11c86a4 100644 --- a/webapp/src/component/common/avatar/AvatarEditDialog.tsx +++ b/webapp/src/component/common/avatar/AvatarEditDialog.tsx @@ -7,7 +7,7 @@ import { DialogTitle, } from '@mui/material'; import { T } from '@tolgee/react'; -import { AvatarEdit } from './AvatarEdit'; +import { AvatarEdit, CropperOptions } from './AvatarEdit'; import Button from '@mui/material/Button'; import LoadingButton from '../form/LoadingButton'; @@ -17,13 +17,18 @@ export const AvatarEditDialog = (props: { onCancel: () => void; onSave: () => void; isUploading: boolean; + cropperProps?: Partial; }) => ( - + + {step !== 1 ? ( + + ) : ( + + {t('order_translation_submit_button')} + + )} + + + ); + }} + + + ); +}; diff --git a/webapp/src/ee/orderTranslations/ProviderDescription.tsx b/webapp/src/ee/orderTranslations/ProviderDescription.tsx new file mode 100644 index 0000000000..19b3b4a0aa --- /dev/null +++ b/webapp/src/ee/orderTranslations/ProviderDescription.tsx @@ -0,0 +1,28 @@ +import { Box, Link as MuiLink } from '@mui/material'; +import ReactMarkdown from 'react-markdown'; + +type Props = { + description: string; +}; + +export const ProviderDescription: React.FC = ({ description }) => { + return ( + + ( + + {props.children} + + ), + }} + > + {description} + + + ); +}; diff --git a/webapp/src/ee/orderTranslations/TranslationAgency.tsx b/webapp/src/ee/orderTranslations/TranslationAgency.tsx new file mode 100644 index 0000000000..1c87a6447f --- /dev/null +++ b/webapp/src/ee/orderTranslations/TranslationAgency.tsx @@ -0,0 +1,94 @@ +import { Box, styled } from '@mui/material'; +import { ProviderDescription } from './ProviderDescription'; +import clsx from 'clsx'; +import { LinkExternal01 } from '@untitled-ui/icons-react'; +import { isValidHttpUrl } from 'tg.fixtures/isValidUrl'; +import { components } from 'tg.service/billingApiSchema.generated'; + +type TranslationAgencyModel = components['schemas']['TranslationAgencyModel']; + +const StyledLinkExternal01 = styled(LinkExternal01)` + margin-left: 3px; + position: relative; + top: 2px; +`; + +const StyledContainer = styled(Box)` + border: 1px solid ${({ theme }) => theme.palette.tokens.border.soft}; + gap: 20px; + display: grid; + border-radius: 16px; + background: ${({ theme }) => theme.palette.tokens.background['paper-2']}; + padding: 20px; + cursor: pointer; + transition: box-shadow ease-in-out 0.2s, border-color ease-in-out 0.2s; + &.selected { + border-color: ${({ theme }) => theme.palette.primary.main}; + cursor: unset; + box-shadow: 0px 0px 17px 0px + ${({ theme }) => theme.palette.primary.main + '55'}; + } +`; + +const StyledServices = styled(Box)` + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); +`; + +const StyledDescription = styled(Box)` + background: ${({ theme }) => theme.palette.tokens.background['paper-3']}; + padding: 0px 16px; + border-radius: 16px; +`; + +type Props = { + provider: TranslationAgencyModel; + selected: boolean; + onSelect: (id: number) => void; +}; + +export const TranslationAgency = ({ provider, selected, onSelect }: Props) => { + const url = + provider.url && isValidHttpUrl(provider.url) + ? new URL(provider.url) + : undefined; + return ( + onSelect(provider.id)} + > + + {provider.avatar ? ( + {provider.name} + ) : ( +

{provider.name}

+ )} + + {url && ( + + {url.host} + + + )} + +
+ {Boolean(provider.services.length) && ( + + {provider.services.map((item) => ( + {item} + ))} + + )} + {provider.description && ( + + + + )} +
+ ); +}; diff --git a/webapp/src/ee/task/components/TasksBoard.tsx b/webapp/src/ee/task/components/TasksBoard.tsx index d4872e8ab1..3fcca05a7d 100644 --- a/webapp/src/ee/task/components/TasksBoard.tsx +++ b/webapp/src/ee/task/components/TasksBoard.tsx @@ -9,6 +9,7 @@ import { useTaskStateTranslation } from 'tg.translationTools/useTaskStateTransla import { useProjectBoardTasks } from '../views/projectTasks/useProjectBoardTasks'; import { useStateColor } from './TaskState'; import { BoardColumn } from './BoardColumn'; +import { PaidFeatureBanner } from 'tg.ee/common/PaidFeatureBanner'; type TaskModel = components['schemas']['TaskModel']; type SimpleProjectModel = components['schemas']['SimpleProjectModel']; @@ -61,11 +62,10 @@ export const TasksBoard = ({ inProgressTasks.hasNextPage && inProgressTasks.fetchNextPage(); doneTasks.hasNextPage && doneTasks.fetchNextPage(); } + const loadables = [newTasks, inProgressTasks, doneTasks]; - const isLoading = - newTasks.isLoading || inProgressTasks.isLoading || doneTasks.isLoading; - const isFetching = - newTasks.isFetching || inProgressTasks.isFetching || doneTasks.isFetching; + const isLoading = loadables.some((l) => l.isLoading); + const isFetching = loadables.some((l) => l.isFetching); if (isLoading) { return ( @@ -75,6 +75,19 @@ export const TasksBoard = ({ ); } + const allReady = loadables.every((l) => l.isFetched); + const allEmpty = loadables.every( + (l) => l.data?.pages?.[0].page?.totalElements === 0 + ); + + if (allReady && allEmpty) { + return ( + + + + ); + } + return ( diff --git a/webapp/src/ee/task/components/taskCreate/TaskCreateDialog.tsx b/webapp/src/ee/task/components/taskCreate/TaskCreateDialog.tsx index 52b7ee205d..2690e9d0e7 100644 --- a/webapp/src/ee/task/components/taskCreate/TaskCreateDialog.tsx +++ b/webapp/src/ee/task/components/taskCreate/TaskCreateDialog.tsx @@ -1,14 +1,4 @@ -import { - Box, - Button, - Checkbox, - Dialog, - DialogTitle, - ListItemText, - MenuItem, - styled, - Typography, -} from '@mui/material'; +import { Button, Dialog, DialogTitle, styled } from '@mui/material'; import { T, useTranslate } from '@tolgee/react'; import { Formik } from 'formik'; import { useState } from 'react'; @@ -17,27 +7,18 @@ import { Validation } from 'tg.constants/GlobalValidationSchema'; import { components } from 'tg.service/apiSchema.generated'; import { useApiMutation, useApiQuery } from 'tg.service/http/useQueryApi'; import { messageService } from 'tg.service/MessageService'; -import { useTaskTypeTranslation } from 'tg.translationTools/useTaskTranslation'; import LoadingButton from 'tg.component/common/form/LoadingButton'; -import { Select as FormSelect } from 'tg.component/common/form/fields/Select'; -import { TextField } from 'tg.component/common/form/fields/TextField'; import { FiltersType } from 'tg.component/translation/translationFilters/tools'; -import { TranslationFilters } from 'tg.component/translation/translationFilters/TranslationFilters'; -import { Select } from 'tg.component/common/Select'; import { User } from 'tg.component/UserAccount'; -import { TaskDatePicker } from '../TaskDatePicker'; -import { TaskPreview } from './TaskPreview'; -import { - TranslationStateFilter, - TranslationStateType, -} from './TranslationStateFilter'; +import { TranslationStateType } from './TranslationStateFilter'; +import { useEnabledFeatures } from 'tg.globalContext/helpers'; +import { PaidFeatureBanner } from 'tg.ee/common/PaidFeatureBanner'; +import { TaskCreateForm } from './TaskCreateForm'; type TaskType = components['schemas']['TaskModel']['type']; type LanguageModel = components['schemas']['LanguageModel']; -const TASK_TYPES: TaskType[] = ['TRANSLATE', 'REVIEW']; - const StyledMainTitle = styled(DialogTitle)` padding-bottom: 0px; `; @@ -55,26 +36,6 @@ const StyledContainer = styled('div')` width: min(calc(100vw - 64px), 800px); `; -const StyledTopPart = styled(Box)` - display: grid; - gap: ${({ theme }) => theme.spacing(0.5, 2)}; - grid-template-columns: 3fr 5fr; - align-items: start; - ${({ theme }) => theme.breakpoints.down('sm')} { - grid-template-columns: 1fr; - } -`; - -const StyledFilters = styled(Box)` - display: grid; - gap: ${({ theme }) => theme.spacing(0.5, 2)}; - grid-template-columns: 3fr 3fr 2fr; - ${({ theme }) => theme.breakpoints.down('sm')} { - grid-template-columns: 1fr; - gap: ${({ theme }) => theme.spacing(2)}; - } -`; - const StyledActions = styled('div')` display: flex; gap: 8px; @@ -111,8 +72,6 @@ export const TaskCreateDialog = ({ }: Props) => { const { t } = useTranslate(); - const translateTaskType = useTaskTypeTranslation(); - const createTasksLoadable = useApiMutation({ url: '/v2/projects/{projectId}/tasks/create-multiple-tasks', method: 'post', @@ -122,6 +81,9 @@ export const TaskCreateDialog = ({ const [filters, setFilters] = useState({}); const [stateFilters, setStateFilters] = useState([]); const [languages, setLanguages] = useState(initialValues?.languages ?? []); + const { features } = useEnabledFeatures(); + + const taskFeature = features.includes('TASKS'); const selectedLoadable = useApiQuery({ url: '/v2/projects/{projectId}/translations/select-all', @@ -139,8 +101,13 @@ export const TaskCreateDialog = ({ const selectedKeys = initialValues?.selection ?? selectedLoadable.data?.ids ?? []; + const disabled = !taskFeature; + return ( + {!taskFeature && ( + + )} @@ -195,130 +162,25 @@ export const TaskCreateDialog = ({ ); }} > - {({ values, setFieldValue, submitForm }) => { + {({ submitForm }) => { return ( - - translateTaskType(v)} - fullWidth - data-cy="create-task-field-type" - > - {TASK_TYPES.map((v) => ( - - {translateTaskType(v)} - - ))} - - - - setFieldValue('dueDate', value)} - /> - - - - - {t('create_task_tasks_and_assignees_title')} - - - {!initialValues?.selection && ( - - languages.includes(l.id) - )} - placeholder={t('create_task_filter_keys_placeholder')} - filterOptions={{ keyRelatedOnly: true }} - sx={{ width: '100%', maxWidth: '270px' }} - /> - )} - - - - {allLanguages && ( - - {languages?.map((language) => ( - l.id === language)!} - type={values.type} - keys={selectedKeys} - assigness={values.assignees[language] ?? []} - onUpdateAssignees={(users) => { - setFieldValue(`assignees[${language}]`, users); - }} - filters={stateFilters} - projectId={projectId} - /> - ))} - - )} theme.spacing(0.5, 2)}; + grid-template-columns: 3fr 5fr; + align-items: start; + ${({ theme }) => theme.breakpoints.down('sm')} { + grid-template-columns: 1fr; + } +`; + +const StyledFilters = styled(Box)` + display: grid; + gap: ${({ theme }) => theme.spacing(0.5, 2)}; + grid-template-columns: 3fr 3fr 2fr; + ${({ theme }) => theme.breakpoints.down('sm')} { + grid-template-columns: 1fr; + gap: ${({ theme }) => theme.spacing(2)}; + } +`; + +type Props = { + selectedKeys: number[]; + disabled?: boolean; + languages: number[]; + setLanguages: (languages: number[]) => void; + allLanguages: LanguageModel[]; + filters: FiltersType; + setFilters?: (filters: FiltersType) => void; + stateFilters: TranslationStateType[]; + setStateFilters: (filters: TranslationStateType[]) => void; + projectId: number; + hideDueDate?: boolean; + hideAssignees?: boolean; +}; + +export const TaskCreateForm = ({ + selectedKeys, + disabled, + languages, + setLanguages, + allLanguages, + filters, + setFilters, + stateFilters, + setStateFilters, + projectId, + hideDueDate, + hideAssignees, +}: Props) => { + const { t } = useTranslate(); + const translateTaskType = useTaskTypeTranslation(); + + const { values, setFieldValue } = useFormikContext(); + + return ( + <> + + translateTaskType(v)} + fullWidth + data-cy="create-task-field-type" + disabled={disabled} + > + {TASK_TYPES.map((v) => ( + + {translateTaskType(v)} + + ))} + + + + {!hideDueDate && ( + + {(field, form) => ( + form.setFieldValue(field.name, value)} + /> + )} + + )} + + + + {!disabled && ( + <> + + {t('create_task_tasks_and_assignees_title')} + + + {setFilters && ( + + languages.includes(l.id) + )} + placeholder={t('create_task_filter_keys_placeholder')} + filterOptions={{ keyRelatedOnly: true }} + sx={{ width: '100%', maxWidth: '270px' }} + /> + )} + + + + {allLanguages && ( + + {languages?.map((language) => ( + l.id === language)!} + type={values.type} + keys={selectedKeys} + assigness={values.assignees[language] ?? []} + onUpdateAssignees={(users) => { + setFieldValue(`assignees[${language}]`, users); + }} + filters={stateFilters} + projectId={projectId} + hideAssignees={hideAssignees} + /> + ))} + + )} + + )} + + ); +}; diff --git a/webapp/src/ee/task/components/taskCreate/TaskPreview.tsx b/webapp/src/ee/task/components/taskCreate/TaskPreview.tsx index 129c9dd845..51b2fe6695 100644 --- a/webapp/src/ee/task/components/taskCreate/TaskPreview.tsx +++ b/webapp/src/ee/task/components/taskCreate/TaskPreview.tsx @@ -18,7 +18,6 @@ type LanguageModel = components['schemas']['LanguageModel']; const StyledContainer = styled('div')` display: grid; padding: 16px 20px; - grid-template-columns: 1fr 3fr 2fr; border-radius: 8px; background: ${({ theme }) => theme.palette.tokens.background.selected}; ${({ theme }) => theme.breakpoints.down('sm')} { @@ -53,6 +52,7 @@ type Props = { onUpdateAssignees: (users: User[]) => void; filters: TranslationStateType[]; projectId: number; + hideAssignees?: boolean; }; export const TaskPreview = ({ @@ -63,6 +63,7 @@ export const TaskPreview = ({ onUpdateAssignees, filters, projectId, + hideAssignees, }: Props) => { const { t } = useTranslate(); const formatNumber = useNumberFormatter(); @@ -84,7 +85,12 @@ export const TaskPreview = ({ }); return ( - + - - {t('create_task_preview_assignee')} - - } - filters={{ - filterMinimalScope: 'TRANSLATIONS_VIEW', - filterViewLanguageId: language.id, - }} - /> + {!hideAssignees && ( + + {t('create_task_preview_assignee')} + + } + filters={{ + filterMinimalScope: 'TRANSLATIONS_VIEW', + filterViewLanguageId: language.id, + }} + /> + )} ); }; diff --git a/webapp/src/ee/task/components/tasksHeader/TasksHeaderBig.tsx b/webapp/src/ee/task/components/tasksHeader/TasksHeaderBig.tsx index 78d4fea557..6c1c1e283f 100644 --- a/webapp/src/ee/task/components/tasksHeader/TasksHeaderBig.tsx +++ b/webapp/src/ee/task/components/tasksHeader/TasksHeaderBig.tsx @@ -48,6 +48,7 @@ type Props = { filter: TaskFilterType; onFilterChange: (value: TaskFilterType) => void; onAddTask?: () => void; + onOrderTranslation?: () => void; view: TaskView; onViewChange: (view: TaskView) => void; project?: SimpleProjectModel; @@ -62,6 +63,7 @@ export const TasksHeaderBig = ({ filter, onFilterChange, onAddTask, + onOrderTranslation, view, onViewChange, project, @@ -110,7 +112,7 @@ export const TasksHeaderBig = ({ } /> - + + {onOrderTranslation && ( + + )} + {onAddTask && ( + )} + {addDialog && ( + setAddDialog(false)} + onFinished={() => setAddDialog(false)} + initialValues={{ + languages: allLanguages + .filter((l) => languagesPreference.includes(l.tag)) + .filter((l) => !l.base) + .map((l) => l.id), + }} + projectId={project.id} + allLanguages={allLanguages} + /> + )} + {orderDialog && ( + setOrderDialog(false)} + onFinished={() => setOrderDialog(false)} + initialValues={{ + languages: allLanguages + .filter((l) => languagesPreference.includes(l.tag)) + .filter((l) => !l.base) + .map((l) => l.id), + }} + projectId={project.id} + allLanguages={allLanguages} + /> + )} + ); }; diff --git a/webapp/src/fixtures/isValidUrl.ts b/webapp/src/fixtures/isValidUrl.ts new file mode 100644 index 0000000000..e494b5b944 --- /dev/null +++ b/webapp/src/fixtures/isValidUrl.ts @@ -0,0 +1,8 @@ +export function isValidHttpUrl(link: string) { + try { + const url = new URL(link); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch (_) { + return false; + } +} diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts index cd63011a2f..b5a812fc50 100644 --- a/webapp/src/service/apiSchema.generated.ts +++ b/webapp/src/service/apiSchema.generated.ts @@ -339,6 +339,12 @@ export interface paths { /** Pairs user account with slack account. */ post: operations["userLogin"]; }; + "/v2/public/translator/translate": { + post: operations["translate"]; + }; + "/v2/public/telemetry/report": { + post: operations["report"]; + }; "/v2/public/slack": { post: operations["slackCommand"]; }; @@ -354,8 +360,26 @@ export interface paths { */ post: operations["fetchBotEvent"]; }; + "/v2/public/licensing/subscription": { + post: operations["getMySubscription"]; + }; + "/v2/public/licensing/set-key": { + post: operations["onLicenceSetKey"]; + }; + "/v2/public/licensing/report-usage": { + post: operations["reportUsage"]; + }; + "/v2/public/licensing/report-error": { + post: operations["reportError"]; + }; + "/v2/public/licensing/release-key": { + post: operations["releaseKey"]; + }; + "/v2/public/licensing/prepare-set-key": { + post: operations["prepareSetLicenseKey"]; + }; "/v2/public/business-events/report": { - post: operations["report"]; + post: operations["report_1"]; }; "/v2/public/business-events/identify": { post: operations["identify"]; @@ -434,7 +458,7 @@ export interface paths { }; "/v2/projects/{projectId}/start-batch-job/pre-translate-by-tm": { /** Pre-translate provided keys to provided languages by TM. */ - post: operations["translate"]; + post: operations["translate_1"]; }; "/v2/projects/{projectId}/start-batch-job/machine-translate": { /** Translate provided keys to provided languages through primary MT provider. */ @@ -520,7 +544,7 @@ export interface paths { }; "/v2/ee-license/prepare-set-license-key": { /** Get info about the upcoming EE subscription. This will show, how much the subscription will cost when key is applied. */ - post: operations["prepareSetLicenseKey"]; + post: operations["prepareSetLicenseKey_1"]; }; "/v2/api-keys": { get: operations["allByUser"]; @@ -630,7 +654,11 @@ export interface paths { get: operations["getMachineTranslationLanguageInfo"]; }; "/v2/projects/{projectId}/keys/search": { - /** This endpoint helps you to find desired key by keyName, base translation or translation in specified language. */ + /** + * This endpoint helps you to find desired key by keyName, base translation or translation in specified language. + * + * Sort is ignored for this request. + */ get: operations["searchForKey"]; }; "/v2/projects/{projectId}/all-keys": { @@ -1186,11 +1214,6 @@ export interface components { * @example 200001,200004 */ permittedLanguageIds?: number[]; - /** - * @description List of languages user can view. If null, all languages view is permitted. - * @example 200001,200004 - */ - viewLanguageIds?: number[]; /** * @description Granted scopes to the user. When user has type permissions, this field contains permission scopes of the type. * @example KEYS_EDIT,TRANSLATIONS_VIEW @@ -1225,6 +1248,11 @@ export interface components { | "tasks.view" | "tasks.edit" )[]; + /** + * @description List of languages user can view. If null, all languages view is permitted. + * @example 200001,200004 + */ + viewLanguageIds?: number[]; }; LanguageModel: { /** Format: int64 */ @@ -1814,8 +1842,8 @@ export interface components { secretKey?: string; endpoint: string; signingRegion: string; - contentStorageType?: "S3" | "AZURE"; enabled?: boolean; + contentStorageType?: "S3" | "AZURE"; }; AzureContentStorageConfigModel: { containerName?: string; @@ -1865,10 +1893,8 @@ export interface components { languages?: string[]; /** @description Format to export to */ format: - | "CSV" | "JSON" | "JSON_TOLGEE" - | "JSON_I18NEXT" | "XLIFF" | "PO" | "APPLE_STRINGS_STRINGSDICT" @@ -1877,7 +1903,9 @@ export interface components { | "FLUTTER_ARB" | "PROPERTIES" | "YAML_RUBY" - | "YAML"; + | "YAML" + | "JSON_I18NEXT" + | "CSV"; /** * @description Delimiter to structure file content. * @@ -1965,10 +1993,8 @@ export interface components { languages?: string[]; /** @description Format to export to */ format: - | "CSV" | "JSON" | "JSON_TOLGEE" - | "JSON_I18NEXT" | "XLIFF" | "PO" | "APPLE_STRINGS_STRINGSDICT" @@ -1977,7 +2003,9 @@ export interface components { | "FLUTTER_ARB" | "PROPERTIES" | "YAML_RUBY" - | "YAML"; + | "YAML" + | "JSON_I18NEXT" + | "CSV"; /** * @description Delimiter to structure file content. * @@ -2255,12 +2283,12 @@ export interface components { id: number; description: string; /** Format: int64 */ + expiresAt?: number; + /** Format: int64 */ createdAt: number; /** Format: int64 */ updatedAt: number; /** Format: int64 */ - expiresAt?: number; - /** Format: int64 */ lastUsedAt?: number; }; SetOrganizationRoleDto: { @@ -2400,10 +2428,8 @@ export interface components { key: string; /** Format: int64 */ id: number; - userFullName?: string; - projectName: string; - description: string; username?: string; + description: string; scopes: string[]; /** Format: int64 */ projectId: number; @@ -2411,6 +2437,8 @@ export interface components { expiresAt?: number; /** Format: int64 */ lastUsedAt?: number; + projectName: string; + userFullName?: string; }; SuperTokenRequest: { /** @description Has to be provided when TOTP enabled */ @@ -2422,6 +2450,49 @@ export interface components { name: string; oldSlug?: string; }; + ExampleItem: { + source: string; + target: string; + key: string; + keyNamespace?: string; + }; + Metadata: { + examples: components["schemas"]["ExampleItem"][]; + closeItems: components["schemas"]["ExampleItem"][]; + keyDescription?: string; + projectDescription?: string; + languageDescription?: string; + }; + TolgeeTranslateParams: { + text: string; + keyName?: string; + sourceTag: string; + targetTag: string; + metadata?: components["schemas"]["Metadata"]; + formality?: "FORMAL" | "INFORMAL" | "DEFAULT"; + isBatch: boolean; + pluralForms?: { [key: string]: string }; + pluralFormExamples?: { [key: string]: string }; + }; + MtResult: { + translated?: string; + /** Format: int32 */ + price: number; + contextDescription?: string; + }; + TelemetryReportRequest: { + instanceId: string; + /** Format: int64 */ + projectsCount: number; + /** Format: int64 */ + translationsCount: number; + /** Format: int64 */ + languagesCount: number; + /** Format: int64 */ + distinctLanguagesCount: number; + /** Format: int64 */ + usersCount: number; + }; SlackCommandDto: { token?: string; team_id: string; @@ -2434,6 +2505,127 @@ export interface components { trigger_id?: string; team_domain: string; }; + GetMySubscriptionDto: { + licenseKey: string; + instanceId: string; + }; + PlanIncludedUsageModel: { + /** Format: int64 */ + seats: number; + /** Format: int64 */ + translationSlots: number; + /** Format: int64 */ + translations: number; + /** Format: int64 */ + mtCredits: number; + }; + PlanPricesModel: { + perSeat: number; + perThousandTranslations?: number; + perThousandMtCredits?: number; + subscriptionMonthly: number; + subscriptionYearly: number; + }; + SelfHostedEePlanModel: { + /** Format: int64 */ + id: number; + name: string; + public: boolean; + enabledFeatures: ( + | "GRANULAR_PERMISSIONS" + | "PRIORITIZED_FEATURE_REQUESTS" + | "PREMIUM_SUPPORT" + | "DEDICATED_SLACK_CHANNEL" + | "ASSISTED_UPDATES" + | "DEPLOYMENT_ASSISTANCE" + | "BACKUP_CONFIGURATION" + | "TEAM_TRAINING" + | "ACCOUNT_MANAGER" + | "STANDARD_SUPPORT" + | "PROJECT_LEVEL_CONTENT_STORAGES" + | "WEBHOOKS" + | "MULTIPLE_CONTENT_DELIVERY_CONFIGS" + | "AI_PROMPT_CUSTOMIZATION" + | "SLACK_INTEGRATION" + | "TASKS" + )[]; + prices: components["schemas"]["PlanPricesModel"]; + includedUsage: components["schemas"]["PlanIncludedUsageModel"]; + hasYearlyPrice: boolean; + free: boolean; + }; + SelfHostedEeSubscriptionModel: { + /** Format: int64 */ + id: number; + /** Format: int64 */ + currentPeriodStart?: number; + /** Format: int64 */ + currentPeriodEnd?: number; + currentBillingPeriod: "MONTHLY" | "YEARLY"; + /** Format: int64 */ + createdAt: number; + plan: components["schemas"]["SelfHostedEePlanModel"]; + status: + | "ACTIVE" + | "CANCELED" + | "PAST_DUE" + | "UNPAID" + | "ERROR" + | "KEY_USED_BY_ANOTHER_INSTANCE"; + licenseKey?: string; + estimatedCosts?: number; + }; + SetLicenseKeyLicensingDto: { + licenseKey: string; + /** Format: int64 */ + seats: number; + instanceId: string; + }; + ReportUsageDto: { + licenseKey: string; + /** Format: int64 */ + seats: number; + }; + ReportErrorDto: { + stackTrace: string; + licenseKey: string; + }; + ReleaseKeyDto: { + licenseKey: string; + }; + PrepareSetLicenseKeyDto: { + licenseKey: string; + /** Format: int64 */ + seats: number; + }; + AverageProportionalUsageItemModel: { + total: number; + unusedQuantity: number; + usedQuantity: number; + usedQuantityOverPlan: number; + }; + PrepareSetEeLicenceKeyModel: { + plan: components["schemas"]["SelfHostedEePlanModel"]; + usage: components["schemas"]["UsageModel"]; + }; + SumUsageItemModel: { + total: number; + /** Format: int64 */ + unusedQuantity: number; + /** Format: int64 */ + usedQuantity: number; + /** Format: int64 */ + usedQuantityOverPlan: number; + }; + UsageModel: { + subscriptionPrice?: number; + /** @description Relevant for invoices only. When there are applied stripe credits, we need to reduce the total price by this amount. */ + appliedStripeCredits?: number; + seats: components["schemas"]["AverageProportionalUsageItemModel"]; + translations: components["schemas"]["AverageProportionalUsageItemModel"]; + credits?: components["schemas"]["SumUsageItemModel"]; + total: number; + }; BusinessEventReportRequest: { eventName: string; anonymousUserId?: string; @@ -3106,10 +3298,8 @@ export interface components { languages?: string[]; /** @description Format to export to */ format: - | "CSV" | "JSON" | "JSON_TOLGEE" - | "JSON_I18NEXT" | "XLIFF" | "PO" | "APPLE_STRINGS_STRINGSDICT" @@ -3118,7 +3308,9 @@ export interface components { | "FLUTTER_ARB" | "PROPERTIES" | "YAML_RUBY" - | "YAML"; + | "YAML" + | "JSON_I18NEXT" + | "CSV"; /** * @description Delimiter to structure file content. * @@ -3290,79 +3482,6 @@ export interface components { createdAt: string; location?: string; }; - AverageProportionalUsageItemModel: { - total: number; - unusedQuantity: number; - usedQuantity: number; - usedQuantityOverPlan: number; - }; - PlanIncludedUsageModel: { - /** Format: int64 */ - seats: number; - /** Format: int64 */ - translationSlots: number; - /** Format: int64 */ - translations: number; - /** Format: int64 */ - mtCredits: number; - }; - PlanPricesModel: { - perSeat: number; - perThousandTranslations?: number; - perThousandMtCredits?: number; - subscriptionMonthly: number; - subscriptionYearly: number; - }; - PrepareSetEeLicenceKeyModel: { - plan: components["schemas"]["SelfHostedEePlanModel"]; - usage: components["schemas"]["UsageModel"]; - }; - SelfHostedEePlanModel: { - /** Format: int64 */ - id: number; - name: string; - public: boolean; - enabledFeatures: ( - | "GRANULAR_PERMISSIONS" - | "PRIORITIZED_FEATURE_REQUESTS" - | "PREMIUM_SUPPORT" - | "DEDICATED_SLACK_CHANNEL" - | "ASSISTED_UPDATES" - | "DEPLOYMENT_ASSISTANCE" - | "BACKUP_CONFIGURATION" - | "TEAM_TRAINING" - | "ACCOUNT_MANAGER" - | "STANDARD_SUPPORT" - | "PROJECT_LEVEL_CONTENT_STORAGES" - | "WEBHOOKS" - | "MULTIPLE_CONTENT_DELIVERY_CONFIGS" - | "AI_PROMPT_CUSTOMIZATION" - | "SLACK_INTEGRATION" - | "TASKS" - )[]; - prices: components["schemas"]["PlanPricesModel"]; - includedUsage: components["schemas"]["PlanIncludedUsageModel"]; - hasYearlyPrice: boolean; - free: boolean; - }; - SumUsageItemModel: { - total: number; - /** Format: int64 */ - unusedQuantity: number; - /** Format: int64 */ - usedQuantity: number; - /** Format: int64 */ - usedQuantityOverPlan: number; - }; - UsageModel: { - subscriptionPrice?: number; - /** @description Relevant for invoices only. When there are applied stripe credits, we need to reduce the total price by this amount. */ - appliedStripeCredits?: number; - seats: components["schemas"]["AverageProportionalUsageItemModel"]; - translations: components["schemas"]["AverageProportionalUsageItemModel"]; - credits?: components["schemas"]["SumUsageItemModel"]; - total: number; - }; CreateApiKeyDto: { /** Format: int64 */ projectId: number; @@ -3582,6 +3701,11 @@ export interface components { name: string; /** Format: int64 */ id: number; + /** @example This is a beautiful organization full of beautiful and clever people */ + description?: string; + /** @example btforg */ + slug: string; + avatar?: components["schemas"]["Avatar"]; /** * @description The role of currently authorized user. * @@ -3589,11 +3713,6 @@ export interface components { */ currentUserRole?: "MEMBER" | "OWNER"; basePermissions: components["schemas"]["PermissionModel"]; - /** @example This is a beautiful organization full of beautiful and clever people */ - description?: string; - avatar?: components["schemas"]["Avatar"]; - /** @example btforg */ - slug: string; }; PublicBillingConfigurationDTO: { enabled: boolean; @@ -3639,10 +3758,8 @@ export interface components { }; ExportFormatModel: { format: - | "CSV" | "JSON" | "JSON_TOLGEE" - | "JSON_I18NEXT" | "XLIFF" | "PO" | "APPLE_STRINGS_STRINGSDICT" @@ -3651,7 +3768,9 @@ export interface components { | "FLUTTER_ARB" | "PROPERTIES" | "YAML_RUBY" - | "YAML"; + | "YAML" + | "JSON_I18NEXT" + | "CSV"; extension: string; mediaType: string; defaultFileStructureTemplate: string; @@ -3757,20 +3876,20 @@ export interface components { name: string; /** Format: int64 */ id: number; - baseTranslation?: string; namespace?: string; description?: string; translation?: string; + baseTranslation?: string; }; KeySearchSearchResultModel: { view?: components["schemas"]["KeySearchResultView"]; name: string; /** Format: int64 */ id: number; - baseTranslation?: string; namespace?: string; description?: string; translation?: string; + baseTranslation?: string; }; PagedModelKeySearchSearchResultModel: { _embedded?: { @@ -4342,12 +4461,12 @@ export interface components { id: number; description: string; /** Format: int64 */ + expiresAt?: number; + /** Format: int64 */ createdAt: number; /** Format: int64 */ updatedAt: number; /** Format: int64 */ - expiresAt?: number; - /** Format: int64 */ lastUsedAt?: number; }; PagedModelOrganizationModel: { @@ -4467,10 +4586,8 @@ export interface components { permittedLanguageIds?: number[]; /** Format: int64 */ id: number; - userFullName?: string; - projectName: string; - description: string; username?: string; + description: string; scopes: string[]; /** Format: int64 */ projectId: number; @@ -4478,6 +4595,8 @@ export interface components { expiresAt?: number; /** Format: int64 */ lastUsedAt?: number; + projectName: string; + userFullName?: string; }; PagedModelUserAccountModel: { _embedded?: { @@ -9447,11 +9566,386 @@ export interface operations { }; responses: { /** OK */ - 200: { - content: { - "application/json": components["schemas"]["OrganizationModel"]; - }; - }; + 200: { + content: { + "application/json": components["schemas"]["OrganizationModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "multipart/form-data": { + /** Format: binary */ + avatar: string; + }; + }; + }; + }; + removeAvatar_2: { + parameters: { + path: { + id: number; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["OrganizationModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; + setLicenseKey: { + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["EeSubscriptionModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SetLicenseKeyDto"]; + }; + }; + }; + /** This will remove the licence key from the instance. */ + release: { + responses: { + /** OK */ + 200: unknown; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; + /** This will refresh the subscription information from the license server and update the subscription info. */ + refreshSubscription: { + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["EeSubscriptionModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; + update_9: { + parameters: { + path: { + apiKeyId: number; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["ApiKeyModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["V2EditApiKeyDto"]; + }; + }; + }; + delete_13: { + parameters: { + path: { + apiKeyId: number; + }; + }; + responses: { + /** OK */ + 200: unknown; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; + regenerate_1: { + parameters: { + path: { + apiKeyId: number; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["RevealedApiKeyModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RegenerateApiKeyDto"]; + }; + }; + }; + /** Enables previously disabled user. */ + enableUser: { + parameters: { + path: { + userId: number; + }; + }; + responses: { + /** OK */ + 200: unknown; /** Bad Request */ 400: { content: { @@ -9485,28 +9979,17 @@ export interface operations { }; }; }; - requestBody: { - content: { - "multipart/form-data": { - /** Format: binary */ - avatar: string; - }; - }; - }; }; - removeAvatar_2: { + /** Disables user account. User will not be able to log in, but their user data will be preserved, so you can enable the user later using the `enable` endpoint. */ + disableUser: { parameters: { path: { - id: number; + userId: number; }; }; responses: { /** OK */ - 200: { - content: { - "application/json": components["schemas"]["OrganizationModel"]; - }; - }; + 200: unknown; /** Bad Request */ 400: { content: { @@ -9541,14 +10024,17 @@ export interface operations { }; }; }; - setLicenseKey: { + /** Set's the global role on the Tolgee Platform server. */ + setRole: { + parameters: { + path: { + userId: number; + role: "USER" | "ADMIN"; + }; + }; responses: { /** OK */ - 200: { - content: { - "application/json": components["schemas"]["EeSubscriptionModel"]; - }; - }; + 200: unknown; /** Bad Request */ 400: { content: { @@ -9582,14 +10068,9 @@ export interface operations { }; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["SetLicenseKeyDto"]; - }; - }; }; - /** This will remove the licence key from the instance. */ - release: { + /** Resends email verification email to currently authenticated user. */ + sendEmailVerification: { responses: { /** OK */ 200: unknown; @@ -9627,13 +10108,13 @@ export interface operations { }; }; }; - /** This will refresh the subscription information from the license server and update the subscription info. */ - refreshSubscription: { + /** Generates new JWT token permitted to sensitive operations */ + getSuperToken: { responses: { /** OK */ 200: { content: { - "application/json": components["schemas"]["EeSubscriptionModel"]; + "application/json": components["schemas"]["JwtAuthenticationResponse"]; }; }; /** Bad Request */ @@ -9669,18 +10150,18 @@ export interface operations { }; }; }; - }; - update_9: { - parameters: { - path: { - apiKeyId: number; + requestBody: { + content: { + "application/json": components["schemas"]["SuperTokenRequest"]; }; }; + }; + generateProjectSlug: { responses: { /** OK */ 200: { content: { - "application/json": components["schemas"]["ApiKeyModel"]; + "application/json": string; }; }; /** Bad Request */ @@ -9718,19 +10199,18 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["V2EditApiKeyDto"]; + "application/json": components["schemas"]["GenerateSlugDto"]; }; }; }; - delete_13: { - parameters: { - path: { - apiKeyId: number; - }; - }; + generateOrganizationSlug: { responses: { /** OK */ - 200: unknown; + 200: { + content: { + "application/json": string; + }; + }; /** Bad Request */ 400: { content: { @@ -9764,20 +10244,23 @@ export interface operations { }; }; }; + requestBody: { + content: { + "application/json": components["schemas"]["GenerateSlugDto"]; + }; + }; }; - regenerate_1: { + /** Pairs user account with slack account. */ + userLogin: { parameters: { - path: { - apiKeyId: number; + query: { + /** The encrypted data about the desired connection between Slack account and Tolgee account */ + data: string; }; }; responses: { /** OK */ - 200: { - content: { - "application/json": components["schemas"]["RevealedApiKeyModel"]; - }; - }; + 200: unknown; /** Bad Request */ 400: { content: { @@ -9811,22 +10294,15 @@ export interface operations { }; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["RegenerateApiKeyDto"]; - }; - }; }; - /** Enables previously disabled user. */ - enableUser: { - parameters: { - path: { - userId: number; - }; - }; + translate: { responses: { /** OK */ - 200: unknown; + 200: { + content: { + "application/json": components["schemas"]["MtResult"]; + }; + }; /** Bad Request */ 400: { content: { @@ -9860,14 +10336,13 @@ export interface operations { }; }; }; - }; - /** Disables user account. User will not be able to log in, but their user data will be preserved, so you can enable the user later using the `enable` endpoint. */ - disableUser: { - parameters: { - path: { - userId: number; + requestBody: { + content: { + "application/json": components["schemas"]["TolgeeTranslateParams"]; }; }; + }; + report: { responses: { /** OK */ 200: unknown; @@ -9904,18 +10379,26 @@ export interface operations { }; }; }; + requestBody: { + content: { + "application/json": components["schemas"]["TelemetryReportRequest"]; + }; + }; }; - /** Set's the global role on the Tolgee Platform server. */ - setRole: { + slackCommand: { parameters: { - path: { - userId: number; - role: "USER" | "ADMIN"; + header: { + "X-Slack-Signature": string; + "X-Slack-Request-Timestamp": string; }; }; responses: { /** OK */ - 200: unknown; + 200: { + content: { + "application/json": string; + }; + }; /** Bad Request */ 400: { content: { @@ -9949,9 +10432,23 @@ export interface operations { }; }; }; + requestBody: { + content: { + "application/json": { + payload?: components["schemas"]["SlackCommandDto"]; + body?: string; + }; + }; + }; }; - /** Resends email verification email to currently authenticated user. */ - sendEmailVerification: { + /** This is triggered when interactivity event is triggered. E.g., when user clicks button provided in previous messages. */ + onInteractivityEvent: { + parameters: { + header: { + "X-Slack-Signature": string; + "X-Slack-Request-Timestamp": string; + }; + }; responses: { /** OK */ 200: unknown; @@ -9988,14 +10485,29 @@ export interface operations { }; }; }; + requestBody: { + content: { + "application/json": string; + }; + }; }; - /** Generates new JWT token permitted to sensitive operations */ - getSuperToken: { + /** + * This is triggered when bot event is triggered. E.g., when app is uninstalled from workspace. + * + * Heads up! The events have to be configured via Slack App configuration in Event Subscription section. + */ + fetchBotEvent: { + parameters: { + header: { + "X-Slack-Signature": string; + "X-Slack-Request-Timestamp": string; + }; + }; responses: { /** OK */ 200: { content: { - "application/json": components["schemas"]["JwtAuthenticationResponse"]; + "application/json": { [key: string]: unknown }; }; }; /** Bad Request */ @@ -10033,16 +10545,16 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["SuperTokenRequest"]; + "application/json": string; }; }; }; - generateProjectSlug: { + getMySubscription: { responses: { /** OK */ 200: { content: { - "application/json": string; + "application/json": components["schemas"]["SelfHostedEeSubscriptionModel"]; }; }; /** Bad Request */ @@ -10080,16 +10592,16 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["GenerateSlugDto"]; + "application/json": components["schemas"]["GetMySubscriptionDto"]; }; }; }; - generateOrganizationSlug: { + onLicenceSetKey: { responses: { /** OK */ 200: { content: { - "application/json": string; + "application/json": components["schemas"]["SelfHostedEeSubscriptionModel"]; }; }; /** Bad Request */ @@ -10127,18 +10639,11 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["GenerateSlugDto"]; + "application/json": components["schemas"]["SetLicenseKeyLicensingDto"]; }; }; }; - /** Pairs user account with slack account. */ - userLogin: { - parameters: { - query: { - /** The encrypted data about the desired connection between Slack account and Tolgee account */ - data: string; - }; - }; + reportUsage: { responses: { /** OK */ 200: unknown; @@ -10175,21 +10680,16 @@ export interface operations { }; }; }; - }; - slackCommand: { - parameters: { - header: { - "X-Slack-Signature": string; - "X-Slack-Request-Timestamp": string; + requestBody: { + content: { + "application/json": components["schemas"]["ReportUsageDto"]; }; }; + }; + reportError: { responses: { /** OK */ - 200: { - content: { - "application/json": string; - }; - }; + 200: unknown; /** Bad Request */ 400: { content: { @@ -10225,21 +10725,11 @@ export interface operations { }; requestBody: { content: { - "application/json": { - payload?: components["schemas"]["SlackCommandDto"]; - body?: string; - }; + "application/json": components["schemas"]["ReportErrorDto"]; }; }; }; - /** This is triggered when interactivity event is triggered. E.g., when user clicks button provided in previous messages. */ - onInteractivityEvent: { - parameters: { - header: { - "X-Slack-Signature": string; - "X-Slack-Request-Timestamp": string; - }; - }; + releaseKey: { responses: { /** OK */ 200: unknown; @@ -10278,27 +10768,16 @@ export interface operations { }; requestBody: { content: { - "application/json": string; + "application/json": components["schemas"]["ReleaseKeyDto"]; }; }; }; - /** - * This is triggered when bot event is triggered. E.g., when app is uninstalled from workspace. - * - * Heads up! The events have to be configured via Slack App configuration in Event Subscription section. - */ - fetchBotEvent: { - parameters: { - header: { - "X-Slack-Signature": string; - "X-Slack-Request-Timestamp": string; - }; - }; + prepareSetLicenseKey: { responses: { /** OK */ 200: { content: { - "application/json": { [key: string]: unknown }; + "application/json": components["schemas"]["PrepareSetEeLicenceKeyModel"]; }; }; /** Bad Request */ @@ -10336,11 +10815,11 @@ export interface operations { }; requestBody: { content: { - "application/json": string; + "application/json": components["schemas"]["PrepareSetLicenseKeyDto"]; }; }; }; - report: { + report_1: { responses: { /** OK */ 200: unknown; @@ -11837,7 +12316,7 @@ export interface operations { }; }; /** Pre-translate provided keys to provided languages by TM. */ - translate: { + translate_1: { parameters: { path: { projectId: number; @@ -12266,10 +12745,8 @@ export interface operations { languages?: string[]; /** Format to export to */ format?: - | "CSV" | "JSON" | "JSON_TOLGEE" - | "JSON_I18NEXT" | "XLIFF" | "PO" | "APPLE_STRINGS_STRINGSDICT" @@ -12278,7 +12755,9 @@ export interface operations { | "FLUTTER_ARB" | "PROPERTIES" | "YAML_RUBY" - | "YAML"; + | "YAML" + | "JSON_I18NEXT" + | "CSV"; /** * Delimiter to structure file content. * @@ -13355,7 +13834,7 @@ export interface operations { }; }; /** Get info about the upcoming EE subscription. This will show, how much the subscription will cost when key is applied. */ - prepareSetLicenseKey: { + prepareSetLicenseKey_1: { responses: { /** OK */ 200: { @@ -14840,7 +15319,11 @@ export interface operations { }; }; }; - /** This endpoint helps you to find desired key by keyName, base translation or translation in specified language. */ + /** + * This endpoint helps you to find desired key by keyName, base translation or translation in specified language. + * + * Sort is ignored for this request. + */ searchForKey: { parameters: { query: { diff --git a/webapp/src/service/billingApiSchema.generated.ts b/webapp/src/service/billingApiSchema.generated.ts index 380c591b6b..b2648671c1 100644 --- a/webapp/src/service/billingApiSchema.generated.ts +++ b/webapp/src/service/billingApiSchema.generated.ts @@ -20,6 +20,14 @@ export interface paths { /** When applied, current subscription will be cancelled at the period end. */ put: operations["cancelSubscription"]; }; + "/v2/administration/billing/translation-agency/{agencyId}": { + put: operations["update"]; + delete: operations["delete"]; + }; + "/v2/administration/billing/translation-agency/{agencyId}/avatar": { + put: operations["uploadAvatar"]; + delete: operations["removeAvatar"]; + }; "/v2/administration/billing/self-hosted-ee-plans/{planId}": { get: operations["getPlan"]; put: operations["updatePlan"]; @@ -30,6 +38,9 @@ export interface paths { put: operations["updatePlan_1"]; delete: operations["deletePlan_1"]; }; + "/v2/administration/billing/add-usage-items-to-invoice-and-finalize-it/{invoiceId}": { + put: operations["addUsageItemsToInvoiceAndFinalizeIt"]; + }; "/v2/organizations/{organizationId}/billing/subscribe": { post: operations["subscribe"]; }; @@ -43,13 +54,16 @@ export interface paths { "/v2/organizations/{organizationId}/billing/buy-more-credits": { post: operations["getBuyMoreCreditsCheckoutSessionUrl"]; }; + "/v2/administration/billing/translation-agency": { + post: operations["create"]; + }; "/v2/administration/billing/self-hosted-ee-plans": { get: operations["getPlans_1"]; - post: operations["create"]; + post: operations["create_1"]; }; "/v2/administration/billing/cloud-plans": { get: operations["getPlans_2"]; - post: operations["create_1"]; + post: operations["create_2"]; }; "/v2/public/billing/plans": { get: operations["getPlans"]; @@ -92,6 +106,12 @@ export interface paths { "/v2/organizations/{organizationId}/billing/billing-info": { get: operations["getBillingInfo"]; }; + "/v2/billing/translation-agency": { + get: operations["getAll"]; + }; + "/v2/billing/translation-agency/{agencyId}": { + get: operations["get"]; + }; "/v2/administration/billing/stripe-products": { get: operations["getStripeProducts"]; }; @@ -493,6 +513,25 @@ export interface components { prorationDate: number; endingBalance: number; }; + UpdateTranslationAgencyRequest: { + name: string; + description: string; + services: string[]; + url: string; + }; + Avatar: { + large: string; + thumbnail: string; + }; + TranslationAgencyModel: { + /** Format: int64 */ + id: number; + name: string; + description?: string; + services: string[]; + url?: string; + avatar?: components["schemas"]["Avatar"]; + }; PlanIncludedUsageRequest: { /** Format: int64 */ seats: number; @@ -672,6 +711,12 @@ export interface components { BuyMoreCreditsModel: { url: string; }; + CreateTranslationAgencyRequest: { + name: string; + description: string; + services: string[]; + url: string; + }; CollectionModelCloudPlanModel: { _embedded?: { plans?: components["schemas"]["CloudPlanModel"][]; @@ -763,6 +808,12 @@ export interface components { vatNo?: string; email?: string; }; + PagedModelTranslationAgencyModel: { + _embedded?: { + translationAgencies?: components["schemas"]["TranslationAgencyModel"][]; + }; + page?: components["schemas"]["PageMetadata"]; + }; CollectionModelStripeProductModel: { _embedded?: { stripeProducts?: components["schemas"]["StripeProductModel"][]; @@ -779,11 +830,6 @@ export interface components { plans?: components["schemas"]["SelfHostedEePlanAdministrationModel"][]; }; }; - /** @example Links to avatar images */ - Avatar: { - large: string; - thumbnail: string; - }; PagedModelSimpleOrganizationModel: { _embedded?: { organizations?: components["schemas"]["SimpleOrganizationModel"][]; @@ -1110,6 +1156,195 @@ export interface operations { }; }; }; + update: { + parameters: { + path: { + agencyId: number; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["TranslationAgencyModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateTranslationAgencyRequest"]; + }; + }; + }; + delete: { + parameters: { + path: { + agencyId: number; + }; + }; + responses: { + /** OK */ + 200: unknown; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; + uploadAvatar: { + parameters: { + path: { + agencyId: number; + }; + }; + responses: { + /** OK */ + 200: unknown; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "multipart/form-data": { + /** Format: binary */ + avatar: string; + }; + }; + }; + }; + removeAvatar: { + parameters: { + path: { + agencyId: number; + }; + }; + responses: { + /** OK */ + 200: unknown; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; getPlan: { parameters: { path: { @@ -1394,6 +1629,49 @@ export interface operations { }; }; }; + addUsageItemsToInvoiceAndFinalizeIt: { + parameters: { + path: { + invoiceId: string; + }; + }; + responses: { + /** OK */ + 200: unknown; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; subscribe: { parameters: { path: { @@ -1649,6 +1927,53 @@ export interface operations { }; }; }; + create: { + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["TranslationAgencyModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateTranslationAgencyRequest"]; + }; + }; + }; getPlans_1: { responses: { /** OK */ @@ -1691,7 +2016,7 @@ export interface operations { }; }; }; - create: { + create_1: { responses: { /** OK */ 200: { @@ -1780,7 +2105,7 @@ export interface operations { }; }; }; - create_1: { + create_2: { responses: { /** OK */ 200: { @@ -2443,6 +2768,106 @@ export interface operations { }; }; }; + getAll: { + parameters: { + query: { + /** Zero-based page index (0..N) */ + page?: number; + /** The size of the page to be returned */ + size?: number; + /** Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + search?: string; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["PagedModelTranslationAgencyModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; + get: { + parameters: { + path: { + agencyId: number; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["TranslationAgencyModel"]; + }; + }; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + }; getStripeProducts: { responses: { /** OK */ diff --git a/webapp/src/views/administration/AdministrationView.tsx b/webapp/src/views/administration/AdministrationView.tsx index 4fac1b3921..7c0e2511c1 100644 --- a/webapp/src/views/administration/AdministrationView.tsx +++ b/webapp/src/views/administration/AdministrationView.tsx @@ -4,14 +4,17 @@ import { PrivateRoute } from 'tg.component/common/PrivateRoute'; import { LINKS } from 'tg.constants/links'; import { AdministrationOrganizations } from './AdministrationOrganizations'; import { AdministrationUsers } from './AdministrationUsers'; -import { AdministrationEeLicenseView } from 'tg.ee/billing/administration/AdministrationEeLicenseView'; -import { AdministrationCloudPlansView } from 'tg.ee/billing/administration/AdministrationCloudPlansView'; -import { AdministrationCloudPlanEditView } from 'tg.ee/billing/administration/AdministrationCloudPlanEditView'; -import { AdministrationCloudPlanCreateView } from 'tg.ee/billing/administration/AdministrationCloudPlanCreateView'; -import { AdministrationEePlansView } from 'tg.ee/billing/administration/AdministrationEePlansView'; -import { AdministrationEePlanEditView } from 'tg.ee/billing/administration/AdministrationEePlanEditView'; -import { AdministrationEePlanCreateView } from 'tg.ee/billing/administration/AdministrationEePlanCreateView'; +import { AdministrationEeLicenseView } from 'tg.ee/billing/administration/subscriptionPlans/AdministrationEeLicenseView'; +import { AdministrationCloudPlansView } from 'tg.ee/billing/administration/subscriptionPlans/AdministrationCloudPlansView'; +import { AdministrationCloudPlanEditView } from 'tg.ee/billing/administration/subscriptionPlans/AdministrationCloudPlanEditView'; +import { AdministrationCloudPlanCreateView } from 'tg.ee/billing/administration/subscriptionPlans/AdministrationCloudPlanCreateView'; +import { AdministrationEePlansView } from 'tg.ee/billing/administration/subscriptionPlans/AdministrationEePlansView'; +import { AdministrationEePlanEditView } from 'tg.ee/billing/administration/subscriptionPlans/AdministrationEePlanEditView'; +import { AdministrationEePlanCreateView } from 'tg.ee/billing/administration/subscriptionPlans/AdministrationEePlanCreateView'; import { useUrlSearchState } from 'tg.hooks/useUrlSearchState'; +import { AdministrationEeTAView } from 'tg.ee/billing/administration/translationAgencies/AdministrationEeTAView'; +import { AdministrationEeTACreateView } from 'tg.ee/billing/administration/translationAgencies/AdministrationEeTACreateView'; +import { AdministrationEeTAEditView } from 'tg.ee/billing/administration/translationAgencies/AdministrationEeTAEditView'; export const AdministrationView = () => { const [search, setSearch] = useUrlSearchState('search'); @@ -34,6 +37,15 @@ export const AdministrationView = () => { + + + + + + + + + = ({ link: LINKS.ADMINISTRATION_EE_LICENSE.build(), label: t('administration_ee_license'), }, + { + link: LINKS.ADMINISTRATION_EE_TA.build(), + label: t('administration_ee_translation_agencies'), + }, ]; if (config.billing.enabled) { From 75777a45bfce0068013edd18e7781bfcdc9d3570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Wed, 23 Oct 2024 16:15:06 +0200 Subject: [PATCH 2/4] feat: order professional translation --- e2e/cypress/support/dataCyType.d.ts | 1 + .../AdministrationEeTACreateView.tsx | 10 +++++++--- .../translationAgencies/TAEditForm.tsx | 13 +++++++++++-- .../components/BaseAdministrationView.tsx | 8 ++++---- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/e2e/cypress/support/dataCyType.d.ts b/e2e/cypress/support/dataCyType.d.ts index 4e06b5eb9b..26d3b6c580 100644 --- a/e2e/cypress/support/dataCyType.d.ts +++ b/e2e/cypress/support/dataCyType.d.ts @@ -32,6 +32,7 @@ declare namespace DataCy { "administration-debug-customer-exit-button" | "administration-ee-license-key-input" | "administration-ee-license-release-key-button" | + "administration-ee-plan-cancel-button" | "administration-ee-plan-field-feature" | "administration-ee-plan-field-free" | "administration-ee-plan-field-included-mt-credits" | diff --git a/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx b/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx index a0dc62611c..1c92590fb9 100644 --- a/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx +++ b/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx @@ -1,7 +1,7 @@ import { Box, Typography } from '@mui/material'; import { DashboardPage } from 'tg.component/layout/DashboardPage'; -import { LINKS } from 'tg.constants/links'; +import { LINKS, PARAMS } from 'tg.constants/links'; import { useBillingApiMutation } from 'tg.service/http/useQueryApi'; import { BaseAdministrationView } from 'tg.views/administration/components/BaseAdministrationView'; import { TAEditForm } from './TAEditForm'; @@ -41,8 +41,12 @@ export const AdministrationEeTACreateView = () => { }, }, { - onSuccess() { - history.push(LINKS.ADMINISTRATION_EE_TA.build()); + onSuccess(data) { + history.push( + LINKS.ADMINISTRATION_EE_TA_EDIT.build({ + [PARAMS.TA_ID]: data.id, + }) + ); }, } ); diff --git a/webapp/src/ee/billing/administration/translationAgencies/TAEditForm.tsx b/webapp/src/ee/billing/administration/translationAgencies/TAEditForm.tsx index d0c1edfa49..98f1dbc733 100644 --- a/webapp/src/ee/billing/administration/translationAgencies/TAEditForm.tsx +++ b/webapp/src/ee/billing/administration/translationAgencies/TAEditForm.tsx @@ -1,10 +1,12 @@ -import { Box } from '@mui/material'; +import { Box, Button } from '@mui/material'; import { useTranslate } from '@tolgee/react'; import { Form, Formik } from 'formik'; import { TextField } from 'tg.component/common/form/fields/TextField'; import LoadingButton from 'tg.component/common/form/LoadingButton'; import React from 'react'; +import { Link } from 'react-router-dom'; +import { LINKS } from 'tg.constants/links'; export type TAFormData = { name: string; @@ -93,7 +95,14 @@ export function TAEditForm({ data-cy="administration-ee-translation-agencies-field-services" /> - + + = ({ link: LINKS.ADMINISTRATION_EE_LICENSE.build(), label: t('administration_ee_license'), }, - { - link: LINKS.ADMINISTRATION_EE_TA.build(), - label: t('administration_ee_translation_agencies'), - }, ]; if (config.billing.enabled) { + menuItems.push({ + link: LINKS.ADMINISTRATION_EE_TA.build(), + label: t('administration_ee_translation_agencies'), + }); menuItems.push({ link: LINKS.ADMINISTRATION_BILLING_CLOUD_PLANS.build(), label: t('administration_cloud_plans'), From e719b8bf398774455279854aa9badab0e474506b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Wed, 23 Oct 2024 16:16:59 +0200 Subject: [PATCH 3/4] feat: order professional translation --- .../AdministrationEeTACreateView.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx b/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx index 1c92590fb9..a0dc62611c 100644 --- a/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx +++ b/webapp/src/ee/billing/administration/translationAgencies/AdministrationEeTACreateView.tsx @@ -1,7 +1,7 @@ import { Box, Typography } from '@mui/material'; import { DashboardPage } from 'tg.component/layout/DashboardPage'; -import { LINKS, PARAMS } from 'tg.constants/links'; +import { LINKS } from 'tg.constants/links'; import { useBillingApiMutation } from 'tg.service/http/useQueryApi'; import { BaseAdministrationView } from 'tg.views/administration/components/BaseAdministrationView'; import { TAEditForm } from './TAEditForm'; @@ -41,12 +41,8 @@ export const AdministrationEeTACreateView = () => { }, }, { - onSuccess(data) { - history.push( - LINKS.ADMINISTRATION_EE_TA_EDIT.build({ - [PARAMS.TA_ID]: data.id, - }) - ); + onSuccess() { + history.push(LINKS.ADMINISTRATION_EE_TA.build()); }, } ); From a2f3b46aad93947c7436e883938fda48c2f51154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Wed, 23 Oct 2024 17:29:20 +0200 Subject: [PATCH 4/4] feat: tasks with agency model --- .../hateoas/TranslationAgencySimpleModel.kt | 12 ++ .../io/tolgee/hateoas/task/TaskModel.kt | 2 + .../tolgee/model/views/TaskWithScopeView.kt | 2 + .../ee/api/v2/controllers/TaskController.kt | 26 +-- .../hateoas/assemblers/TaskModelAssembler.kt | 2 + .../TranslationAgencySimpleModelAssembler.kt | 26 +++ .../task/CreateTranslationOrderRequest.kt | 6 + .../io/tolgee/ee/service/TaskService.kt | 10 +- .../OrderTranslationsDialog.tsx | 42 +++-- .../orderTranslations/TranslationAgency.tsx | 24 ++- webapp/src/ee/task/components/TaskLabel.tsx | 17 ++ webapp/src/service/apiSchema.generated.ts | 162 +++++++++++++----- 12 files changed, 243 insertions(+), 88 deletions(-) create mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/TranslationAgencySimpleModel.kt create mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TranslationAgencySimpleModelAssembler.kt create mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/data/task/CreateTranslationOrderRequest.kt diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/TranslationAgencySimpleModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/TranslationAgencySimpleModel.kt new file mode 100644 index 0000000000..219ea54ece --- /dev/null +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/TranslationAgencySimpleModel.kt @@ -0,0 +1,12 @@ +package io.tolgee.hateoas + +import io.tolgee.dtos.Avatar +import org.springframework.hateoas.RepresentationModel + +data class TranslationAgencySimpleModel( + var id: Long = 0L, + var name: String = "", + var url: String? = "", + val avatar: Avatar?, +): RepresentationModel() + diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/task/TaskModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/task/TaskModel.kt index d971a271e9..84d5456f32 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/task/TaskModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/task/TaskModel.kt @@ -1,5 +1,6 @@ package io.tolgee.hateoas.task +import io.tolgee.hateoas.TranslationAgencySimpleModel import io.tolgee.hateoas.language.LanguageModel import io.tolgee.hateoas.userAccount.SimpleUserAccountModel import io.tolgee.model.enums.TaskState @@ -24,4 +25,5 @@ class TaskModel( var createdAt: Long? = 0, var closedAt: Long? = null, var state: TaskState = TaskState.IN_PROGRESS, + var agency: TranslationAgencySimpleModel? = null, ) : RepresentationModel() diff --git a/backend/data/src/main/kotlin/io/tolgee/model/views/TaskWithScopeView.kt b/backend/data/src/main/kotlin/io/tolgee/model/views/TaskWithScopeView.kt index 667042af01..ae3f8b308c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/views/TaskWithScopeView.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/views/TaskWithScopeView.kt @@ -6,6 +6,7 @@ import io.tolgee.model.UserAccount import io.tolgee.model.enums.TaskState import io.tolgee.model.enums.TaskType import io.tolgee.model.task.TaskKey +import io.tolgee.model.translationAgency.TranslationAgency import java.util.* data class TaskWithScopeView( @@ -26,4 +27,5 @@ data class TaskWithScopeView( val doneItems: Long, val baseWordCount: Long, val baseCharacterCount: Long, + val agency: TranslationAgency?, ) diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/TaskController.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/TaskController.kt index 5a27de2646..22689667cc 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/TaskController.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/TaskController.kt @@ -9,16 +9,7 @@ import io.tolgee.constants.Feature import io.tolgee.dtos.request.userAccount.UserAccountPermissionsFilters import io.tolgee.ee.api.v2.hateoas.assemblers.TaskModelAssembler import io.tolgee.ee.api.v2.hateoas.assemblers.TaskPerUserReportModelAssembler -import io.tolgee.ee.data.task.CalculateScopeRequest -import io.tolgee.ee.data.task.CreateMultipleTasksRequest -import io.tolgee.ee.data.task.CreateTaskRequest -import io.tolgee.ee.data.task.TaskFilters -import io.tolgee.ee.data.task.TaskKeysResponse -import io.tolgee.ee.data.task.TranslationScopeFilters -import io.tolgee.ee.data.task.UpdateTaskKeyRequest -import io.tolgee.ee.data.task.UpdateTaskKeyResponse -import io.tolgee.ee.data.task.UpdateTaskKeysRequest -import io.tolgee.ee.data.task.UpdateTaskRequest +import io.tolgee.ee.data.task.* import io.tolgee.ee.service.TaskService import io.tolgee.hateoas.task.TaskModel import io.tolgee.hateoas.task.TaskPerUserReportModel @@ -118,6 +109,21 @@ class TaskController( taskService.createMultipleTasks(projectHolder.project.id, dto.tasks, filters) } + @PostMapping("/create-translation-order") + @Operation(summary = "Create multiple tasks with assigned agency") + @RequiresProjectPermissions([Scope.TASKS_EDIT]) + @AllowApiAccess + @RequestActivity(ActivityType.TASKS_CREATE) + @OpenApiOrderExtension(2) + fun createTranslationOrder( + @RequestBody @Valid + dto: CreateTranslationOrderRequest, + @ParameterObject + filters: TranslationScopeFilters, + ) { + taskService.createMultipleTasks(projectHolder.project.id, dto.tasks, filters, dto.agencyId) + } + @GetMapping("/{taskNumber}") @Operation(summary = "Get task") // permissions checked inside diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TaskModelAssembler.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TaskModelAssembler.kt index 7fbaba77f8..a11c8cd84b 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TaskModelAssembler.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TaskModelAssembler.kt @@ -13,6 +13,7 @@ import org.springframework.stereotype.Component class TaskModelAssembler( private val simpleUserAccountModelAssembler: SimpleUserAccountModelAssembler, private val languageModelAssembler: LanguageModelAssembler, + private val translationAgencySimpleModelAssembler: TranslationAgencySimpleModelAssembler ) : RepresentationModelAssemblerSupport( TaskController::class.java, TaskModel::class.java, @@ -42,6 +43,7 @@ class TaskModelAssembler( baseWordCount = entity.baseWordCount, baseCharacterCount = entity.baseCharacterCount, state = entity.state, + agency = entity.agency?.let { translationAgencySimpleModelAssembler.toModel(it) } ) } } diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TranslationAgencySimpleModelAssembler.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TranslationAgencySimpleModelAssembler.kt new file mode 100644 index 0000000000..5d4b0d2b30 --- /dev/null +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TranslationAgencySimpleModelAssembler.kt @@ -0,0 +1,26 @@ +package io.tolgee.ee.api.v2.hateoas.assemblers + +import io.tolgee.ee.api.v2.controllers.TaskController +import io.tolgee.hateoas.TranslationAgencySimpleModel +import io.tolgee.model.translationAgency.TranslationAgency +import io.tolgee.service.AvatarService +import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport +import org.springframework.stereotype.Component + +@Component +class TranslationAgencySimpleModelAssembler( + private val avatarService: AvatarService, +) : RepresentationModelAssemblerSupport( + TaskController::class.java, + TranslationAgencySimpleModel::class.java, +) { + override fun toModel(entity: TranslationAgency): TranslationAgencySimpleModel { + return TranslationAgencySimpleModel( + id = entity.id, + name = entity.name, + url = entity.url, + avatar = avatarService.getAvatarLinks(entity.avatarHash), + ) + } +} + diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/task/CreateTranslationOrderRequest.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/task/CreateTranslationOrderRequest.kt new file mode 100644 index 0000000000..2853563261 --- /dev/null +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/task/CreateTranslationOrderRequest.kt @@ -0,0 +1,6 @@ +package io.tolgee.ee.data.task + +class CreateTranslationOrderRequest( + var agencyId: Long, + var tasks: MutableSet = mutableSetOf(), +) diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt index e8247d9743..4d6ce76e36 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt @@ -21,6 +21,7 @@ import io.tolgee.model.enums.TaskState import io.tolgee.model.enums.TaskType import io.tolgee.model.task.Task import io.tolgee.model.task.TaskKey +import io.tolgee.model.translationAgency.TranslationAgency import io.tolgee.model.views.KeysScopeView import io.tolgee.model.views.TaskPerUserReportView import io.tolgee.model.views.TaskWithScopeView @@ -100,9 +101,10 @@ class TaskService( projectId: Long, dtos: Collection, filters: TranslationScopeFilters, + agencyId: Long? = null, ) { dtos.forEach { - createSingleTask(projectId, it, filters) + createSingleTask(projectId, it, filters, agencyId) } } @@ -132,13 +134,14 @@ class TaskService( projectId: Long, dto: CreateTaskRequest, filters: TranslationScopeFilters, + agencyId: Long? = null, ): Task { var lastErr = DataIntegrityViolationException("Error") repeat(10) { // necessary for proper transaction creation try { return executeInNewRepeatableTransaction(platformTransactionManager) { - val task = taskService.createTaskInTransaction(projectId, dto, filters) + val task = taskService.createTaskInTransaction(projectId, dto, filters, agencyId) entityManager.flush() task.assignees.forEach { assigneeNotificationService.notifyNewAssignee(it, task) @@ -157,6 +160,7 @@ class TaskService( projectId: Long, dto: CreateTaskRequest, filters: TranslationScopeFilters, + agencyId: Long? = null, ): Task { val newNumber = getNextTaskNumber(projectId) val language = checkLanguage(dto.languageId!!, projectId) @@ -181,6 +185,7 @@ class TaskService( task.language = language task.assignees = assignees task.author = entityManager.getReference(UserAccount::class.java, authenticationFacade.authenticatedUser.id) + task.agency = agencyId?.let { entityManager.getReference(TranslationAgency::class.java, it) } task.state = TaskState.NEW taskRepository.saveAndFlush(task) val keys = keyService.getByIds(keyIds) @@ -478,6 +483,7 @@ class TaskService( doneItems = scope.doneItems, baseWordCount = scope.baseWordCount, baseCharacterCount = scope.baseCharacterCount, + agency = task.agency, ) } } diff --git a/webapp/src/ee/orderTranslations/OrderTranslationsDialog.tsx b/webapp/src/ee/orderTranslations/OrderTranslationsDialog.tsx index 04bc0c97d6..c3b1d137c6 100644 --- a/webapp/src/ee/orderTranslations/OrderTranslationsDialog.tsx +++ b/webapp/src/ee/orderTranslations/OrderTranslationsDialog.tsx @@ -32,7 +32,8 @@ import { TranslationStateType } from 'tg.ee/task/components/taskCreate/Translati import { TranslationAgency } from './TranslationAgency'; import { BoxLoading } from 'tg.component/common/BoxLoading'; -type TaskType = components['schemas']['TaskModel']['type']; +type CreateTaskRequest = components['schemas']['CreateTaskRequest']; +type TaskType = CreateTaskRequest['type']; type LanguageModel = components['schemas']['LanguageModel']; const StyledMainTitle = styled(DialogTitle)` @@ -101,7 +102,7 @@ export const OrderTranslationsDialog = ({ const { t } = useTranslate(); const createTasksLoadable = useApiMutation({ - url: '/v2/projects/{projectId}/tasks/create-multiple-tasks', + url: '/v2/projects/{projectId}/tasks/create-translation-order', method: 'post', invalidatePrefix: ['/v2/projects/{projectId}/tasks', '/v2/user-tasks'], }); @@ -154,21 +155,24 @@ export const OrderTranslationsDialog = ({ description: initialValues?.description ?? '', dueDate: initialValues?.dueDate ?? undefined, assignees: initialValues?.languageAssignees ?? {}, - provider: undefined as number | undefined, agreeSharing: true, agreeInvite: true, + agencyId: undefined as number | undefined, }} validationSchema={Validation.CREATE_TASK_FORM(t)} onSubmit={async (values) => { - const data = languages.map((languageId) => ({ - type: values.type, - name: values.name, - description: values.description, - languageId: languageId, - dueDate: values.dueDate, - assignees: values.assignees[languageId]?.map((u) => u.id) ?? [], - keys: selectedKeys, - })); + const data = languages.map( + (languageId) => + ({ + type: values.type, + name: values.name, + description: values.description, + languageId: languageId, + dueDate: values.dueDate, + assignees: values.assignees[languageId]?.map((u) => u.id) ?? [], + keys: selectedKeys, + } satisfies CreateTaskRequest) + ); createTasksLoadable.mutate( { path: { projectId }, @@ -177,7 +181,7 @@ export const OrderTranslationsDialog = ({ filterOutdated: stateFilters.includes('OUTDATED'), }, content: { - 'application/json': { tasks: data }, + 'application/json': { tasks: data, agencyId: values.agencyId! }, }, }, { @@ -197,7 +201,7 @@ export const OrderTranslationsDialog = ({ {({ submitForm, values, setFieldValue }) => { const selectedAgency = agenciesLoadable.data?._embedded?.translationAgencies?.find( - (a) => a.id === values.provider + (a) => a.id === values.agencyId ); return ( @@ -230,12 +234,12 @@ export const OrderTranslationsDialog = ({ ) : ( {agenciesLoadable.data?._embedded?.translationAgencies?.map( - (provider, i) => ( + (agency, i) => ( setFieldValue('provider', id)} + agency={agency} + selected={values.agencyId === agency.id} + onSelect={(id) => setFieldValue('agencyId', id)} /> ) )} @@ -306,7 +310,7 @@ export const OrderTranslationsDialog = ({ onClick={() => setStep(1)} color="primary" variant="contained" - disabled={values.provider === undefined} + disabled={values.agencyId === undefined} data-cy="order-translation-next" > {t('order_translation_next_button')} diff --git a/webapp/src/ee/orderTranslations/TranslationAgency.tsx b/webapp/src/ee/orderTranslations/TranslationAgency.tsx index 1c87a6447f..d00e0fc650 100644 --- a/webapp/src/ee/orderTranslations/TranslationAgency.tsx +++ b/webapp/src/ee/orderTranslations/TranslationAgency.tsx @@ -42,20 +42,18 @@ const StyledDescription = styled(Box)` `; type Props = { - provider: TranslationAgencyModel; + agency: TranslationAgencyModel; selected: boolean; onSelect: (id: number) => void; }; -export const TranslationAgency = ({ provider, selected, onSelect }: Props) => { +export const TranslationAgency = ({ agency, selected, onSelect }: Props) => { const url = - provider.url && isValidHttpUrl(provider.url) - ? new URL(provider.url) - : undefined; + agency.url && isValidHttpUrl(agency.url) ? new URL(agency.url) : undefined; return ( onSelect(provider.id)} + onClick={() => onSelect(agency.id)} > { flexWrap="wrap" alignItems="start" > - {provider.avatar ? ( - {provider.name} + {agency.avatar ? ( + {agency.name} ) : ( -

{provider.name}

+

{agency.name}

)} {url && ( @@ -77,16 +75,16 @@ export const TranslationAgency = ({ provider, selected, onSelect }: Props) => { )}
- {Boolean(provider.services.length) && ( + {Boolean(agency.services.length) && ( - {provider.services.map((item) => ( + {agency.services.map((item) => ( {item} ))} )} - {provider.description && ( + {agency.description && ( - + )}
diff --git a/webapp/src/ee/task/components/TaskLabel.tsx b/webapp/src/ee/task/components/TaskLabel.tsx index 97b4624c2d..cc6e3d885d 100644 --- a/webapp/src/ee/task/components/TaskLabel.tsx +++ b/webapp/src/ee/task/components/TaskLabel.tsx @@ -24,6 +24,11 @@ const StyledTaskName = styled(Box)` font-size: 16px; `; +const StyledAgencyName = styled(Box)` + font-size: 16px; + font-weight: 500; +`; + type Props = { task: TaskModel; project?: SimpleProjectModel; @@ -58,6 +63,18 @@ export const TaskLabel = ({ )} {!hideType && } + {task.agency && + (task.agency.avatar ? ( + {task.agency.name} + ) : ( + + {task.agency.name} + + ))}
); }; diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts index b5a812fc50..7442005954 100644 --- a/webapp/src/service/apiSchema.generated.ts +++ b/webapp/src/service/apiSchema.generated.ts @@ -398,6 +398,9 @@ export interface paths { /** Sends a test request to the webhook */ post: operations["test"]; }; + "/v2/projects/{projectId}/tasks/create-translation-order": { + post: operations["createTranslationOrder"]; + }; "/v2/projects/{projectId}/tasks/create-multiple-tasks": { post: operations["createTasks"]; }; @@ -1196,24 +1199,6 @@ export interface components { | "SERVER_ADMIN"; /** @description The user's permission type. This field is null if uses granular permissions */ type?: "NONE" | "VIEW" | "TRANSLATE" | "REVIEW" | "EDIT" | "MANAGE"; - /** - * @description List of languages user can translate to. If null, all languages editing is permitted. - * @example 200001,200004 - */ - translateLanguageIds?: number[]; - /** - * @description List of languages user can change state to. If null, changing state of all language values is permitted. - * @example 200001,200004 - */ - stateChangeLanguageIds?: number[]; - /** - * @deprecated - * @description Deprecated (use translateLanguageIds). - * - * List of languages current user has TRANSLATE permission to. If null, all languages edition is permitted. - * @example 200001,200004 - */ - permittedLanguageIds?: number[]; /** * @description Granted scopes to the user. When user has type permissions, this field contains permission scopes of the type. * @example KEYS_EDIT,TRANSLATIONS_VIEW @@ -1253,6 +1238,24 @@ export interface components { * @example 200001,200004 */ viewLanguageIds?: number[]; + /** + * @description List of languages user can translate to. If null, all languages editing is permitted. + * @example 200001,200004 + */ + translateLanguageIds?: number[]; + /** + * @description List of languages user can change state to. If null, changing state of all language values is permitted. + * @example 200001,200004 + */ + stateChangeLanguageIds?: number[]; + /** + * @deprecated + * @description Deprecated (use translateLanguageIds). + * + * List of languages current user has TRANSLATE permission to. If null, all languages edition is permitted. + * @example 200001,200004 + */ + permittedLanguageIds?: number[]; }; LanguageModel: { /** Format: int64 */ @@ -1434,6 +1437,14 @@ export interface components { /** Format: int64 */ closedAt?: number; state: "NEW" | "IN_PROGRESS" | "DONE" | "CLOSED"; + agency?: components["schemas"]["TranslationAgencySimpleModel"]; + }; + TranslationAgencySimpleModel: { + /** Format: int64 */ + id: number; + name: string; + url?: string; + avatar?: components["schemas"]["Avatar"]; }; UpdateTaskKeyRequest: { done: boolean; @@ -2117,12 +2128,12 @@ export interface components { createNewKeys: boolean; }; ImportSettingsModel: { + /** @description If false, only updates keys, skipping the creation of new keys */ + createNewKeys: boolean; /** @description If true, placeholders from other formats will be converted to ICU when possible */ convertPlaceholdersToIcu: boolean; /** @description If true, key descriptions will be overridden by the import */ overrideKeyDescriptions: boolean; - /** @description If false, only updates keys, skipping the creation of new keys */ - createNewKeys: boolean; }; TranslationCommentModel: { /** @@ -2281,15 +2292,15 @@ export interface components { token: string; /** Format: int64 */ id: number; - description: string; /** Format: int64 */ expiresAt?: number; /** Format: int64 */ + lastUsedAt?: number; + /** Format: int64 */ createdAt: number; /** Format: int64 */ updatedAt: number; - /** Format: int64 */ - lastUsedAt?: number; + description: string; }; SetOrganizationRoleDto: { roleType: "MEMBER" | "OWNER"; @@ -2428,16 +2439,16 @@ export interface components { key: string; /** Format: int64 */ id: number; - username?: string; - description: string; + projectName: string; + /** Format: int64 */ + expiresAt?: number; scopes: string[]; /** Format: int64 */ projectId: number; /** Format: int64 */ - expiresAt?: number; - /** Format: int64 */ lastUsedAt?: number; - projectName: string; + username?: string; + description: string; userFullName?: string; }; SuperTokenRequest: { @@ -2656,9 +2667,6 @@ export interface components { WebhookTestResponse: { success: boolean; }; - CreateMultipleTasksRequest: { - tasks: components["schemas"]["CreateTaskRequest"][]; - }; CreateTaskRequest: { name: string; description: string; @@ -2678,6 +2686,14 @@ export interface components { assignees: number[]; keys: number[]; }; + CreateTranslationOrderRequest: { + /** Format: int64 */ + agencyId: number; + tasks: components["schemas"]["CreateTaskRequest"][]; + }; + CreateMultipleTasksRequest: { + tasks: components["schemas"]["CreateTaskRequest"][]; + }; CalculateScopeRequest: { /** Format: int64 */ languageId: number; @@ -2737,11 +2753,12 @@ export interface components { * * - KEEP: Translation is not changed * - OVERRIDE: Translation is overridden - * - NEW: New translation is created) + * - NEW: New translation is created + * - FORCE_OVERRIDE: Translation is updated, created or kept. * * @example OVERRIDE */ - resolution: "KEEP" | "OVERRIDE" | "NEW"; + resolution: "KEEP" | "OVERRIDE" | "NEW" | "FORCE_OVERRIDE"; }; KeyImportResolvableResultModel: { /** @description List of keys */ @@ -3701,11 +3718,11 @@ export interface components { name: string; /** Format: int64 */ id: number; - /** @example This is a beautiful organization full of beautiful and clever people */ - description?: string; + avatar?: components["schemas"]["Avatar"]; /** @example btforg */ slug: string; - avatar?: components["schemas"]["Avatar"]; + /** @example This is a beautiful organization full of beautiful and clever people */ + description?: string; /** * @description The role of currently authorized user. * @@ -4459,15 +4476,15 @@ export interface components { user: components["schemas"]["SimpleUserAccountModel"]; /** Format: int64 */ id: number; - description: string; /** Format: int64 */ expiresAt?: number; /** Format: int64 */ + lastUsedAt?: number; + /** Format: int64 */ createdAt: number; /** Format: int64 */ updatedAt: number; - /** Format: int64 */ - lastUsedAt?: number; + description: string; }; PagedModelOrganizationModel: { _embedded?: { @@ -4586,16 +4603,16 @@ export interface components { permittedLanguageIds?: number[]; /** Format: int64 */ id: number; - username?: string; - description: string; + projectName: string; + /** Format: int64 */ + expiresAt?: number; scopes: string[]; /** Format: int64 */ projectId: number; /** Format: int64 */ - expiresAt?: number; - /** Format: int64 */ lastUsedAt?: number; - projectName: string; + username?: string; + description: string; userFullName?: string; }; PagedModelUserAccountModel: { @@ -11167,6 +11184,63 @@ export interface operations { }; }; }; + createTranslationOrder: { + parameters: { + query: { + filterState?: ( + | "UNTRANSLATED" + | "TRANSLATED" + | "REVIEWED" + | "DISABLED" + )[]; + filterOutdated?: boolean; + }; + path: { + projectId: number; + }; + }; + responses: { + /** OK */ + 200: unknown; + /** Bad Request */ + 400: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Unauthorized */ + 401: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Forbidden */ + 403: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + /** Not Found */ + 404: { + content: { + "application/json": + | components["schemas"]["ErrorResponseTyped"] + | components["schemas"]["ErrorResponseBody"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateTranslationOrderRequest"]; + }; + }; + }; createTasks: { parameters: { query: {