From ffa82556e0a8837b127b8b5385b1765bb4b72e4c Mon Sep 17 00:00:00 2001
From: waza-ari
Date: Sun, 10 Mar 2024 14:04:32 +0000
Subject: [PATCH] feat(teams): add public flags to teams to allow easier
sharing with other teams (#2179)
Resolves #2173
Co-authored-by: Daniel Herrmann
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2179
Reviewed-by: konrad
Co-authored-by: waza-ari
Co-committed-by: waza-ari
---
config.yml.sample | 3 +
docs/content/doc/setup/config.md | 11 +++
docs/content/doc/setup/openid.md | 17 ++++-
frontend/src/components/sharing/userTeam.vue | 13 +++-
frontend/src/i18n/lang/en.json | 4 +-
frontend/src/modelTypes/ITeam.ts | 1 +
frontend/src/models/team.ts | 1 +
frontend/src/stores/config.ts | 2 +
frontend/src/views/teams/EditTeam.vue | 24 +++++++
frontend/src/views/teams/NewTeam.vue | 25 +++++++
pkg/config/config.go | 2 +
pkg/db/fixtures/team_members.yml | 50 +++++--------
pkg/db/fixtures/teams.yml | 10 ++-
pkg/migration/20240309111148.go | 43 +++++++++++
pkg/models/teams.go | 25 ++++++-
pkg/models/teams_test.go | 75 ++++++++++++++++++++
pkg/modules/auth/openid/openid.go | 36 ++++++++--
pkg/modules/auth/openid/openid_test.go | 37 +++++++++-
pkg/routes/api/v1/info.go | 2 +
pkg/swagger/docs.go | 19 +++++
pkg/swagger/swagger.json | 19 +++++
pkg/swagger/swagger.yaml | 18 +++++
22 files changed, 392 insertions(+), 45 deletions(-)
create mode 100644 pkg/migration/20240309111148.go
diff --git a/config.yml.sample b/config.yml.sample
index acbb5a300..10f30b0d4 100644
--- a/config.yml.sample
+++ b/config.yml.sample
@@ -62,6 +62,9 @@ service:
allowiconchanges: true
# Allow using a custom logo via external URL.
customlogourl: ''
+ # Enables the public team feature. If enabled, it is possible to configure teams to be public, which makes them
+ # discoverable when sharing a project, therefore not only showing teams the user is member of.
+ enablepublicteams: false
sentry:
# If set to true, enables anonymous error tracking of api errors via Sentry. This allows us to gather more
diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md
index a68bf22b5..c5210edec 100644
--- a/docs/content/doc/setup/config.md
+++ b/docs/content/doc/setup/config.md
@@ -346,6 +346,17 @@ Full path: `service.customlogourl`
Environment path: `VIKUNJA_SERVICE_CUSTOMLOGOURL`
+### enablepublicteams
+
+discoverable when sharing a project, therefore not only showing teams the user is member of.
+
+Default: `false`
+
+Full path: `service.enablepublicteams`
+
+Environment path: `VIKUNJA_SERVICE_ENABLEPUBLICTEAMS`
+
+
---
## sentry
diff --git a/docs/content/doc/setup/openid.md b/docs/content/doc/setup/openid.md
index 320c2a725..b0e66e69c 100644
--- a/docs/content/doc/setup/openid.md
+++ b/docs/content/doc/setup/openid.md
@@ -99,7 +99,7 @@ It depends on the provider being used as well as the preferences of the administ
Typically you'd want to request an additional scope (e.g. `vikunja_scope`) which then triggers the identity provider to add the claim.
If the `vikunja_groups` is part of the **ID token**, Vikunja will start the procedure and import teams and team memberships.
-The claim structure expexted by Vikunja is as follows:
+The minimal claim structure expected by Vikunja is as follows:
```json
{
@@ -116,6 +116,21 @@ The claim structure expexted by Vikunja is as follows:
}
```
+It also also possible to pass the description and isPublic flag as optional parameter. If not present, the description will be empty and project visibility defaults to false.
+
+```json
+{
+ "vikunja_groups": [
+ {
+ "name": "team 3",
+ "oidcID": 33349,
+ "description": "My Team Description",
+ "isPublic": true
+ },
+ ]
+}
+```
+
For each team, you need to define a team `name` and an `oidcID`, where the `oidcID` can be any string with a length of less than 250 characters.
The `oidcID` is used to uniquely identify the team, so please make sure to keep this unique.
diff --git a/frontend/src/components/sharing/userTeam.vue b/frontend/src/components/sharing/userTeam.vue
index 3858c6ab5..535b8cdbb 100644
--- a/frontend/src/components/sharing/userTeam.vue
+++ b/frontend/src/components/sharing/userTeam.vue
@@ -172,6 +172,7 @@ import Multiselect from '@/components/input/multiselect.vue'
import Nothing from '@/components/misc/nothing.vue'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
+import {useConfigStore} from '@/stores/config'
// FIXME: I think this whole thing can now only manage user/team sharing for projects? Maybe remove a little generalization?
@@ -210,8 +211,8 @@ const selectedRight = ref({})
const sharables = ref([])
const showDeleteModal = ref(false)
-
const authStore = useAuthStore()
+const configStore = useConfigStore()
const userInfo = computed(() => authStore.info)
function createShareTypeNameComputed(count: number) {
@@ -360,7 +361,15 @@ async function find(query: string) {
found.value = []
return
}
- const results = await searchService.getAll({}, {s: query})
+
+ // Include public teams here if we are sharing with teams and its enabled in the config
+ let results = []
+ if (props.shareType === 'team' && configStore.publicTeamsEnabled) {
+ results = await searchService.getAll({}, {s: query, includePublic: true})
+ } else {
+ results = await searchService.getAll({}, {s: query})
+ }
+
found.value = results
.filter(m => {
if(props.shareType === 'user' && m.id === currentUserId.value) {
diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json
index 652b43450..b7747ecb7 100644
--- a/frontend/src/i18n/lang/en.json
+++ b/frontend/src/i18n/lang/en.json
@@ -986,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
diff --git a/frontend/src/modelTypes/ITeam.ts b/frontend/src/modelTypes/ITeam.ts
index 3cdaae987..e9e7142cb 100644
--- a/frontend/src/modelTypes/ITeam.ts
+++ b/frontend/src/modelTypes/ITeam.ts
@@ -10,6 +10,7 @@ export interface ITeam extends IAbstract {
members: ITeamMember[]
right: Right
oidcId: string
+ isPublic: boolean
createdBy: IUser
created: Date
diff --git a/frontend/src/models/team.ts b/frontend/src/models/team.ts
index 1e75738bb..cc17849fa 100644
--- a/frontend/src/models/team.ts
+++ b/frontend/src/models/team.ts
@@ -14,6 +14,7 @@ export default class TeamModel extends AbstractModel implements ITeam {
members: ITeamMember[] = []
right: Right = RIGHTS.READ
oidcId = ''
+ isPublic: boolean = false
createdBy: IUser = {} // FIXME: seems wrong
created: Date = null
diff --git a/frontend/src/stores/config.ts b/frontend/src/stores/config.ts
index c5dbe6973..eb09372da 100644
--- a/frontend/src/stores/config.ts
+++ b/frontend/src/stores/config.ts
@@ -37,6 +37,7 @@ export interface ConfigState {
providers: IProvider[],
},
},
+ publicTeamsEnabled: boolean,
}
export const useConfigStore = defineStore('config', () => {
@@ -70,6 +71,7 @@ export const useConfigStore = defineStore('config', () => {
providers: [],
},
},
+ publicTeamsEnabled: false,
})
const migratorsEnabled = computed(() => state.availableMigrators?.length > 0)
diff --git a/frontend/src/views/teams/EditTeam.vue b/frontend/src/views/teams/EditTeam.vue
index 993f2e6af..f435d4df8 100644
--- a/frontend/src/views/teams/EditTeam.vue
+++ b/frontend/src/views/teams/EditTeam.vue
@@ -33,6 +33,27 @@
>
{{ $t('team.attributes.nameRequired') }}