Skip to content

Commit b6ac50b

Browse files
committed
feat: implemented glossary create form
1 parent 74c6085 commit b6ac50b

File tree

24 files changed

+1042
-185
lines changed

24 files changed

+1042
-185
lines changed

backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationProjectController.kt

+30-7
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,30 @@ package io.tolgee.api.v2.controllers.organization
66

77
import io.swagger.v3.oas.annotations.Operation
88
import io.swagger.v3.oas.annotations.tags.Tag
9+
import io.tolgee.dtos.cacheable.LanguageDto
10+
import io.tolgee.dtos.cacheable.OrganizationLanguageDto
11+
import io.tolgee.dtos.request.language.LanguageFilters
912
import io.tolgee.exceptions.NotFoundException
1013
import io.tolgee.facade.ProjectWithStatsFacade
14+
import io.tolgee.hateoas.language.LanguageModel
15+
import io.tolgee.hateoas.language.LanguageModelAssembler
16+
import io.tolgee.hateoas.language.OrganizationLanguageModel
17+
import io.tolgee.hateoas.language.OrganizationLanguageModelAssembler
1118
import io.tolgee.hateoas.project.ProjectModel
1219
import io.tolgee.hateoas.project.ProjectModelAssembler
1320
import io.tolgee.hateoas.project.ProjectWithStatsModel
1421
import io.tolgee.model.views.ProjectWithLanguagesView
1522
import io.tolgee.security.authorization.UseDefaultPermissions
23+
import io.tolgee.service.language.LanguageService
1624
import io.tolgee.service.organization.OrganizationService
1725
import io.tolgee.service.project.ProjectService
1826
import org.springdoc.core.annotations.ParameterObject
1927
import org.springframework.data.domain.Pageable
28+
import org.springframework.data.domain.Sort
2029
import org.springframework.data.web.PagedResourcesAssembler
2130
import org.springframework.data.web.SortDefault
2231
import org.springframework.hateoas.PagedModel
23-
import org.springframework.web.bind.annotation.CrossOrigin
24-
import org.springframework.web.bind.annotation.GetMapping
25-
import org.springframework.web.bind.annotation.PathVariable
26-
import org.springframework.web.bind.annotation.RequestMapping
27-
import org.springframework.web.bind.annotation.RequestParam
28-
import org.springframework.web.bind.annotation.RestController
32+
import org.springframework.web.bind.annotation.*
2933

3034
@RestController
3135
@CrossOrigin(origins = ["*"])
@@ -38,6 +42,9 @@ class OrganizationProjectController(
3842
private val projectService: ProjectService,
3943
private val projectModelAssembler: ProjectModelAssembler,
4044
private val projectWithStatsFacade: ProjectWithStatsFacade,
45+
private val languageService: LanguageService,
46+
private val organizationLanguageModelAssembler: OrganizationLanguageModelAssembler,
47+
private val pagedAssembler: PagedResourcesAssembler<OrganizationLanguageDto>,
4148
) {
4249
@GetMapping("/{id:[0-9]+}/projects")
4350
@Operation(
@@ -111,5 +118,21 @@ class OrganizationProjectController(
111118
} ?: throw NotFoundException()
112119
}
113120

114-
// TODO: get getAllLanguagesInUse - for glossaries
121+
@Operation(
122+
summary = "Get all languages in use by projects owned by specified organization",
123+
description = "Returns all languages in use by projects owned by specified organization",
124+
)
125+
@GetMapping("/{organizationId:[0-9]+}/languages")
126+
@UseDefaultPermissions
127+
fun getAllLanguagesInUse(
128+
@ParameterObject
129+
@SortDefault("base", direction = Sort.Direction.DESC)
130+
@SortDefault("tag", direction = Sort.Direction.ASC)
131+
pageable: Pageable,
132+
@RequestParam("search") search: String?,
133+
@PathVariable organizationId: Long,
134+
): PagedModel<OrganizationLanguageModel> {
135+
val languages = languageService.getPagedByOrganization(organizationId, pageable, search)
136+
return pagedAssembler.toModel(languages, organizationLanguageModelAssembler)
137+
}
115138
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.tolgee.hateoas.language
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import org.springframework.hateoas.RepresentationModel
5+
import org.springframework.hateoas.server.core.Relation
6+
7+
@Suppress("unused")
8+
@Relation(collectionRelation = "languages", itemRelation = "language")
9+
open class OrganizationLanguageModel(
10+
@Schema(example = "Czech", description = "Language name in english")
11+
val name: String,
12+
@Schema(example = "cs-CZ", description = "Language tag according to BCP 47 definition")
13+
var tag: String,
14+
@Schema(example = "čeština", description = "Language name in this language")
15+
var originalName: String? = null,
16+
@Schema(example = "\uD83C\uDDE8\uD83C\uDDFF", description = "Language flag emoji as UTF-8 emoji")
17+
var flagEmoji: String? = null,
18+
@Schema(example = "false", description = "Whether is base language of any project")
19+
var base: Boolean,
20+
) : RepresentationModel<OrganizationLanguageModel>()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.tolgee.hateoas.language
2+
3+
import io.tolgee.api.v2.controllers.organization.OrganizationProjectController
4+
import io.tolgee.dtos.cacheable.OrganizationLanguageDto
5+
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
6+
import org.springframework.stereotype.Component
7+
8+
@Component
9+
class OrganizationLanguageModelAssembler : RepresentationModelAssemblerSupport<OrganizationLanguageDto, OrganizationLanguageModel>(
10+
OrganizationProjectController::class.java,
11+
OrganizationLanguageModel::class.java,
12+
) {
13+
override fun toModel(languageDto: OrganizationLanguageDto): OrganizationLanguageModel {
14+
return OrganizationLanguageModel(
15+
name = languageDto.name,
16+
originalName = languageDto.originalName,
17+
tag = languageDto.tag,
18+
flagEmoji = languageDto.flagEmoji,
19+
base = languageDto.base,
20+
)
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.tolgee.dtos.cacheable
2+
3+
interface OrganizationLanguageDto {
4+
val name: String
5+
val tag: String
6+
val originalName: String?
7+
val flagEmoji: String?
8+
val base: Boolean
9+
}

backend/data/src/main/kotlin/io/tolgee/dtos/request/language/LanguageFilters.kt

+5
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,9 @@ open class LanguageFilters {
1212
description = """Filter languages without id""",
1313
)
1414
var filterNotId: List<Long>? = null
15+
16+
@field:Parameter(
17+
description = """Filter languages by name or tag""",
18+
)
19+
var search: String? = null
1520
}

backend/data/src/main/kotlin/io/tolgee/repository/LanguageRepository.kt

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.tolgee.repository
22

33
import io.tolgee.dtos.cacheable.LanguageDto
4+
import io.tolgee.dtos.cacheable.OrganizationLanguageDto
45
import io.tolgee.dtos.request.language.LanguageFilters
56
import io.tolgee.model.Language
67
import org.springframework.context.annotation.Lazy
@@ -22,6 +23,20 @@ const val LANGUAGE_FILTERS = """
2223
)
2324
"""
2425

26+
const val SEARCH_FILTER = """
27+
(
28+
:search is null or (lower(l.name) like lower(concat('%', cast(:search as text), '%'))
29+
or lower(l.tag) like lower(concat('%', cast(:search as text),'%')))
30+
)
31+
"""
32+
33+
const val ORGANIZATION_FILTER = """
34+
o.id = :organizationId
35+
and o.deleted_at is null
36+
and p.deleted_at is null
37+
and l.deleted_at is null
38+
"""
39+
2540
@Repository
2641
@Lazy
2742
interface LanguageRepository : JpaRepository<Language, Long> {
@@ -55,7 +70,7 @@ interface LanguageRepository : JpaRepository<Language, Long> {
5570
l.originalName,
5671
l.flagEmoji,
5772
l.aiTranslatorPromptDescription,
58-
coalesce((l.id = l.project.baseLanguage.id), false)
73+
coalesce((l.id = l.project.baseLanguage.id), false) as base
5974
)
6075
from Language l
6176
where l.project.id = :projectId and l.deletedAt is null
@@ -94,6 +109,62 @@ interface LanguageRepository : JpaRepository<Language, Long> {
94109
languageIds: List<Long>,
95110
): List<Language>
96111

112+
@Query(
113+
"""
114+
with base_distinct_tags AS (
115+
select min(l.id) as id, l.tag as tag
116+
from language l
117+
join project p on p.id = l.project_id
118+
join organization o on p.organization_owner_id = o.id
119+
where $ORGANIZATION_FILTER
120+
and l.id = p.base_language_id
121+
and $SEARCH_FILTER
122+
group by l.tag
123+
),
124+
non_base_distinct_tags AS (
125+
select min(l.id) as id, l.tag as tag
126+
from language l
127+
join project p on p.id = l.project_id
128+
join organization o on p.organization_owner_id = o.id
129+
where $ORGANIZATION_FILTER
130+
and l.id != p.base_language_id
131+
and $SEARCH_FILTER
132+
and l.tag not in (
133+
select tag
134+
from base_distinct_tags
135+
)
136+
group by l.tag
137+
)
138+
select *
139+
from (
140+
select
141+
l.name as name,
142+
l.tag as tag,
143+
l.original_name as originalName,
144+
l.flag_emoji as flagEmoji,
145+
(
146+
CASE
147+
WHEN l.id IN (SELECT id FROM base_distinct_tags) THEN true
148+
ELSE false
149+
END
150+
) as base
151+
from language l
152+
where l.id in (
153+
select id from base_distinct_tags
154+
)
155+
or l.id in (
156+
select id from non_base_distinct_tags
157+
)
158+
) as result
159+
""",
160+
nativeQuery = true,
161+
)
162+
fun findAllByOrganizationId(
163+
organizationId: Long?,
164+
pageable: Pageable,
165+
search: String?,
166+
): Page<OrganizationLanguageDto>
167+
97168
@Query(
98169
"""
99170
select l

backend/data/src/main/kotlin/io/tolgee/service/language/LanguageService.kt

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import io.tolgee.component.CurrentDateProvider
66
import io.tolgee.constants.Caches
77
import io.tolgee.constants.Message
88
import io.tolgee.dtos.cacheable.LanguageDto
9+
import io.tolgee.dtos.cacheable.OrganizationLanguageDto
910
import io.tolgee.dtos.request.LanguageRequest
1011
import io.tolgee.dtos.request.language.LanguageFilters
1112
import io.tolgee.exceptions.NotFoundException
@@ -344,6 +345,14 @@ class LanguageService(
344345
return this.languageRepository.findAllByProjectId(projectId, pageable, filters ?: LanguageFilters())
345346
}
346347

348+
fun getPagedByOrganization(
349+
organizationId: Long,
350+
pageable: Pageable,
351+
search: String?,
352+
): Page<OrganizationLanguageDto> {
353+
return this.languageRepository.findAllByOrganizationId(organizationId, pageable, search)
354+
}
355+
347356
fun findByIdIn(ids: Iterable<Long>): List<Language> {
348357
return languageRepository.findAllById(ids)
349358
}

e2e/cypress/support/dataCyType.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ declare namespace DataCy {
103103
"api-key-list-item-regenerate-button" |
104104
"api-keys-create-edit-dialog" |
105105
"api-keys-project-select-item" |
106+
"assigned-projects-select" |
106107
"assignee-search-select-popover" |
107108
"assignee-select" |
108109
"auto-avatar-img" |
@@ -237,6 +238,7 @@ declare namespace DataCy {
237238
"global-plus-button" |
238239
"global-search-field" |
239240
"global-user-menu-button" |
241+
"glossary-base-language-select" |
240242
"import-conflict-resolution-dialog" |
241243
"import-conflicts-not-resolved-dialog" |
242244
"import-conflicts-not-resolved-dialog-cancel-button" |

webapp/src/component/common/list/SimpleList.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const SimpleList = <
3434
};
3535
renderItem: (item: DataItem) => ReactNode;
3636
itemSeparator?: () => ReactNode;
37-
getKey?: (item: DataItem) => any;
37+
getKey?: (item: DataItem) => React.Key;
3838
} & OverridableListWrappers<WrapperComponent, ListComponent>
3939
) => {
4040
const { data, pagination } = props;

0 commit comments

Comments
 (0)