diff --git a/codegen.json b/codegen.json
index 932fcbe384f..4349e98347d 100644
--- a/codegen.json
+++ b/codegen.json
@@ -92,9 +92,10 @@
"GifResponse": "./types/GifResponse#GifResponseSource",
"GitHubIntegration": "../../postgres/queries/getGitHubAuthByUserIdTeamId#GitHubAuth",
"GitLabIntegration": "./types/GitLabIntegration#GitLabIntegrationSource",
- "IntegrationProviderOAuth1": "../../postgres/queries/getIntegrationProvidersByIds#TIntegrationProvider",
- "IntegrationProviderOAuth2": "../../postgres/queries/getIntegrationProvidersByIds#TIntegrationProvider",
- "IntegrationProviderWebhook": "../../postgres/queries/getIntegrationProvidersByIds#TIntegrationProvider",
+ "IntegrationProvider": "./types/IntegrationProvider#IntegrationProviderSource",
+ "IntegrationProviderOAuth1": "./types/IntegrationProviderOAuth1#IntegrationProviderOAuth1Source",
+ "IntegrationProviderOAuth2": "./types/IntegrationProviderOAuth2#IntegrationProviderOAuth2Source",
+ "IntegrationProviderWebhook": "./types/IntegrationProviderWebhook#IntegrationProviderWebhookSource",
"InviteToTeamPayload": "./types/InviteToTeamPayload#InviteToTeamPayloadSource",
"JiraIssue": "./types/JiraIssue#JiraIssueSource",
"JiraRemoteAvatarUrls": "./types/JiraRemoteAvatarUrls#JiraRemoteAvatarUrlsSource",
@@ -163,9 +164,9 @@
"SetDefaultSlackChannelSuccess": "./types/SetDefaultSlackChannelSuccess#SetDefaultSlackChannelSuccessSource",
"SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource",
"SetNotificationStatusPayload": "./types/SetNotificationStatusPayload#SetNotificationStatusPayloadSource",
- "SetNotificationSettingSuccess": "./types/SetNotificationSettingSuccess#SetNotificationSettingSuccessSource",
"SetOrgUserRoleSuccess": "./types/SetOrgUserRoleSuccess#SetOrgUserRoleSuccessSource",
"SetSlackNotificationPayload": "./types/SetSlackNotificationPayload#SetSlackNotificationPayloadSource",
+ "SetTeamNotificationSettingSuccess": "./types/SetTeamNotificationSettingSuccess#SetTeamNotificationSettingSuccessSource",
"ShareTopicSuccess": "./types/ShareTopicSuccess#ShareTopicSuccessSource",
"SlackIntegration": "../../postgres/types/index#SlackAuth as SlackAuthDB",
"SlackNotification": "../../postgres/types/index#SlackNotification as SlackNotificationDB",
@@ -186,6 +187,7 @@
"TeamMemberIntegrationAuthOAuth2": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth",
"TeamMemberIntegrationAuthWebhook": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth",
"TeamMemberIntegrations": "./types/TeamMemberIntegrations#TeamMemberIntegrationsSource",
+ "TeamNotificationSettings": "./types/TeamNotificationSettings#TeamNotificationSettingsSource",
"TeamPromptMeeting": "../../postgres/types/Meeting#TeamPromptMeeting",
"TeamPromptMeetingMember": "../../postgres/types/Meeting.d#TeamPromptMeetingMember as TeamPromptMeetingMemberDB",
"TeamPromptMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB",
diff --git a/package.json b/package.json
index 6de00dd0954..1f61389e923 100644
--- a/package.json
+++ b/package.json
@@ -108,7 +108,7 @@
"husky": "^7.0.4",
"jscodeshift": "^0.14.0",
"kysely": "^0.27.5",
- "kysely-codegen": "^0.15.0",
+ "kysely-codegen": "^0.17.0",
"kysely-ctl": "^0.11.0",
"lerna": "^6.4.1",
"mini-css-extract-plugin": "^2.7.2",
diff --git a/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx b/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx
index dc220441205..f4751608d10 100644
--- a/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx
+++ b/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx
@@ -61,11 +61,12 @@ const StyledNotificationErrorMessage = styled(NotificationErrorMessage)({
})
const isNotificationActive = (integration: {
- auth: null | undefined | {isActive: boolean; events: readonly SlackNotificationEventEnum[]}
+ isActive: boolean
+ teamNotificationSettings: {events: readonly SlackNotificationEventEnum[]} | null | undefined
}) => {
- const {auth} = integration
- if (!auth?.isActive) return false
- const {events} = auth
+ const {isActive, teamNotificationSettings} = integration
+ if (!isActive || !teamNotificationSettings) return false
+ const {events} = teamNotificationSettings
if (!events) return false
return (
events.includes('MEETING_STAGE_TIME_LIMIT_START') ||
@@ -81,14 +82,16 @@ const StageTimerModalEndTimeSlackToggle = (props: Props) => {
teamId
integrations {
mattermost {
- auth {
- isActive
+ isActive
+ teamNotificationSettings {
+ id
events
}
}
msTeams {
- auth {
- isActive
+ isActive
+ teamNotificationSettings {
+ id
events
}
}
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx
index 51f61b53ddc..bd380e5783e 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx
@@ -73,12 +73,14 @@ const MSTeamsPanel = (props: Props) => {
integrations {
msTeams {
auth {
- ...NotificationSettings_auth
provider {
id
webhookUrl
}
}
+ teamNotificationSettings {
+ ...NotificationSettings_settings
+ }
}
}
}
@@ -89,7 +91,7 @@ const MSTeamsPanel = (props: Props) => {
const {teamMember} = viewer
const {integrations} = teamMember!
const {msTeams} = integrations
- const {auth} = msTeams
+ const {teamNotificationSettings, auth} = msTeams
const activeProvider = auth?.provider
const atmosphere = useAtmosphere()
@@ -209,7 +211,7 @@ const MSTeamsPanel = (props: Props) => {
{fieldError && {fieldError}}
{!fieldError && mutationError && {mutationError.message}}
- {auth && }
+ {teamNotificationSettings && }
)
}
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx
index 36564a80260..a0f4bd69a27 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx
@@ -21,11 +21,13 @@ graphql`
fragment MSTeamsProviderRowTeamMemberIntegrations on TeamMemberIntegrations {
msTeams {
auth {
- ...NotificationSettings_auth
provider {
id
}
}
+ teamNotificationSettings {
+ ...NotificationSettings_settings
+ }
}
}
`
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx
index 6695d38f01e..44be7e23f3e 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx
@@ -87,12 +87,14 @@ const MattermostPanel = (props: Props) => {
integrations {
mattermost {
auth {
- ...NotificationSettings_auth
provider {
id
webhookUrl
}
}
+ teamNotificationSettings {
+ ...NotificationSettings_settings
+ }
}
}
}
@@ -103,7 +105,7 @@ const MattermostPanel = (props: Props) => {
const {teamMember} = viewer
const {integrations} = teamMember!
const {mattermost} = integrations
- const {auth} = mattermost
+ const {teamNotificationSettings, auth} = mattermost
const activeProvider = auth?.provider
const atmosphere = useAtmosphere()
@@ -224,7 +226,7 @@ const MattermostPanel = (props: Props) => {
{fieldError && {fieldError}}
{!fieldError && mutationError && {mutationError.message}}
- {auth && }
+ {teamNotificationSettings && }
)
}
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx
index 82799c82bd5..4709edae839 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx
@@ -21,11 +21,13 @@ graphql`
fragment MattermostProviderRowTeamMemberIntegrations on TeamMemberIntegrations {
mattermost {
auth {
- ...NotificationSettings_auth
provider {
id
}
}
+ teamNotificationSettings {
+ ...NotificationSettings_settings
+ }
}
}
`
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/NotificationSettings.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/NotificationSettings.tsx
index 6672b9d0677..c1cb023a189 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/NotificationSettings.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/NotificationSettings.tsx
@@ -1,14 +1,14 @@
import graphql from 'babel-plugin-relay/macro'
import {useFragment} from 'react-relay'
import {
- NotificationSettings_auth$key,
+ NotificationSettings_settings$key,
SlackNotificationEventEnum
-} from '../../../../__generated__/NotificationSettings_auth.graphql'
+} from '../../../../__generated__/NotificationSettings_settings.graphql'
import StyledError from '../../../../components/StyledError'
import Toggle from '../../../../components/Toggle/Toggle'
import useAtmosphere from '../../../../hooks/useAtmosphere'
import useMutationProps from '../../../../hooks/useMutationProps'
-import SetNotificationSettingMutation from '../../../../mutations/SetNotificationSettingMutation'
+import SetTeamNotificationSettingMutation from '../../../../mutations/SetTeamNotificationSettingMutation'
import {MeetingLabels} from '../../../../types/constEnums'
const EVENTS = [
@@ -31,21 +31,21 @@ const labelLookup = {
} as Record
interface Props {
- auth: NotificationSettings_auth$key
+ settings: NotificationSettings_settings$key
}
const NotificationSettings = (props: Props) => {
- const {auth: authRef} = props
- const auth = useFragment(
+ const {settings: settingsRef} = props
+ const settings = useFragment(
graphql`
- fragment NotificationSettings_auth on TeamMemberIntegrationAuthWebhook {
+ fragment NotificationSettings_settings on TeamNotificationSettings {
id
events
}
`,
- authRef
+ settingsRef
)
- const {events} = auth
+ const {events} = settings
const atmosphere = useAtmosphere()
const {submitting, onError, onCompleted, submitMutation, error} = useMutationProps()
@@ -54,10 +54,10 @@ const NotificationSettings = (props: Props) => {
return
}
submitMutation()
- SetNotificationSettingMutation(
+ SetTeamNotificationSettingMutation(
atmosphere,
{
- authId: auth.id,
+ id: settings.id,
event,
isEnabled
},
diff --git a/packages/client/mutations/SetNotificationSettingMutation.ts b/packages/client/mutations/SetNotificationSettingMutation.ts
deleted file mode 100644
index 3d9ea3a4ac1..00000000000
--- a/packages/client/mutations/SetNotificationSettingMutation.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import graphql from 'babel-plugin-relay/macro'
-import {commitMutation} from 'react-relay'
-import {
- SlackNotificationEventEnum,
- SetNotificationSettingMutation as TSetSlackNotificationMutation
-} from '../__generated__/SetNotificationSettingMutation.graphql'
-import {StandardMutation} from '../types/relayMutations'
-
-graphql`
- fragment SetNotificationSettingMutation_auth on SetNotificationSettingSuccess {
- auth {
- id
- events
- }
- }
-`
-
-const mutation = graphql`
- mutation SetNotificationSettingMutation(
- $authId: ID!
- $event: SlackNotificationEventEnum!
- $isEnabled: Boolean!
- ) {
- setNotificationSetting(authId: $authId, event: $event, isEnabled: $isEnabled) {
- ... on ErrorPayload {
- error {
- message
- }
- }
- ...SetNotificationSettingMutation_auth @relay(mask: false)
- }
- }
-`
-
-const SetSlackNotificationMutation: StandardMutation = (
- atmosphere,
- variables,
- {onError, onCompleted}
-) => {
- return commitMutation(atmosphere, {
- mutation,
- variables,
- optimisticUpdater: (store) => {
- const {authId, event, isEnabled} = variables
- const auth = store.get(authId)
- if (!auth) return
- const enabledEvents = auth.getValue('events') as SlackNotificationEventEnum[]
- if (!enabledEvents) return
- const newEvents = isEnabled
- ? [...enabledEvents, event]
- : enabledEvents.filter((enabledEvent) => enabledEvent !== event)
- auth.setValue(newEvents, 'events')
- },
- onCompleted,
- onError
- })
-}
-
-export default SetSlackNotificationMutation
diff --git a/packages/client/mutations/SetTeamNotificationSettingMutation.ts b/packages/client/mutations/SetTeamNotificationSettingMutation.ts
new file mode 100644
index 00000000000..d87c48c9c6a
--- /dev/null
+++ b/packages/client/mutations/SetTeamNotificationSettingMutation.ts
@@ -0,0 +1,59 @@
+import graphql from 'babel-plugin-relay/macro'
+import {commitMutation} from 'react-relay'
+import {
+ SlackNotificationEventEnum,
+ SetTeamNotificationSettingMutation as TSetTeamNotificationSettingMutation
+} from '../__generated__/SetTeamNotificationSettingMutation.graphql'
+import {StandardMutation} from '../types/relayMutations'
+
+graphql`
+ fragment SetTeamNotificationSettingMutation_settings on SetTeamNotificationSettingSuccess {
+ teamNotificationSettings {
+ id
+ events
+ }
+ }
+`
+
+const mutation = graphql`
+ mutation SetTeamNotificationSettingMutation(
+ $id: ID!
+ $event: SlackNotificationEventEnum!
+ $isEnabled: Boolean!
+ ) {
+ setTeamNotificationSetting(id: $id, event: $event, isEnabled: $isEnabled) {
+ ... on ErrorPayload {
+ error {
+ message
+ }
+ }
+ ...SetTeamNotificationSettingMutation_settings @relay(mask: false)
+ }
+ }
+`
+
+const SetTeamNotificationMutation: StandardMutation = (
+ atmosphere,
+ variables,
+ {onError, onCompleted}
+) => {
+ return commitMutation(atmosphere, {
+ mutation,
+ variables,
+ optimisticUpdater: (store) => {
+ const {id, event, isEnabled} = variables
+ const settings = store.get(id)
+ if (!settings) return
+ const enabledEvents = settings.getValue('events') as SlackNotificationEventEnum[]
+ if (!enabledEvents) return
+ const newEvents = isEnabled
+ ? [...enabledEvents, event]
+ : enabledEvents.filter((enabledEvent) => enabledEvent !== event)
+ settings.setValue(newEvents, 'events')
+ },
+ onCompleted,
+ onError
+ })
+}
+
+export default SetTeamNotificationMutation
diff --git a/packages/client/shared/gqlIds/TeamNotificationSettingsId.ts b/packages/client/shared/gqlIds/TeamNotificationSettingsId.ts
new file mode 100644
index 00000000000..686e2788aca
--- /dev/null
+++ b/packages/client/shared/gqlIds/TeamNotificationSettingsId.ts
@@ -0,0 +1,6 @@
+const TeamNotificationSettingsId = {
+ join: (id: number) => `teamNotificationSettings:${id}`,
+ split: (id: string) => parseInt(id.split(':')[1]!)
+}
+
+export default TeamNotificationSettingsId
diff --git a/packages/client/subscriptions/TeamSubscription.ts b/packages/client/subscriptions/TeamSubscription.ts
index 156fcf2041a..eef3097dab9 100644
--- a/packages/client/subscriptions/TeamSubscription.ts
+++ b/packages/client/subscriptions/TeamSubscription.ts
@@ -152,8 +152,8 @@ const subscription = graphql`
SetMeetingSettingsPayload {
...SetMeetingSettingsMutation_team @relay(mask: false)
}
- SetNotificationSettingSuccess {
- ...SetNotificationSettingMutation_auth @relay(mask: false)
+ SetTeamNotificationSettingSuccess {
+ ...SetTeamNotificationSettingMutation_settings @relay(mask: false)
}
StartCheckInSuccess {
...StartCheckInMutation_team @relay(mask: false)
@@ -170,7 +170,6 @@ const subscription = graphql`
UpdateAgendaItemPayload {
...UpdateAgendaItemMutation_team @relay(mask: false)
}
-
UpdateCreditCardPayload {
...UpdateCreditCardMutation_team @relay(mask: false)
}
diff --git a/packages/server/dataloader/integrationAuthLoaders.ts b/packages/server/dataloader/integrationAuthLoaders.ts
index 9099b920886..3e12ab931a1 100644
--- a/packages/server/dataloader/integrationAuthLoaders.ts
+++ b/packages/server/dataloader/integrationAuthLoaders.ts
@@ -1,4 +1,5 @@
import DataLoader from 'dataloader'
+import {Selectable, sql} from 'kysely'
import errorFilter from '../graphql/errorFilter'
import isValid from '../graphql/isValid'
import getKysely from '../postgres/getKysely'
@@ -8,9 +9,9 @@ import getIntegrationProvidersByIds, {
} from '../postgres/queries/getIntegrationProvidersByIds'
import {selectSlackNotifications, selectTeamMemberIntegrationAuth} from '../postgres/select'
import {SlackAuth, SlackNotification, TeamMemberIntegrationAuth} from '../postgres/types'
-import {NotificationSettings} from '../postgres/types/pg'
+import {TeamNotificationSettings} from '../postgres/types/pg'
import NullableDataLoader from './NullableDataLoader'
-import RootDataLoader from './RootDataLoader'
+import RootDataLoader, {RegisterDependsOn} from './RootDataLoader'
interface TeamMemberIntegrationAuthServiceTeamUserKey {
service: IntegrationProviderServiceEnum
@@ -164,9 +165,9 @@ export const slackNotificationsByTeamIdAndEvent = (parent: RootDataLoader) => {
})
}
-export const teamMemberIntegrationAuthsByTeamIdAndEvent = (parent: RootDataLoader) => {
+export const teamMemberIntegrationAuthsByTeamIdAndService = (parent: RootDataLoader) => {
return new DataLoader<
- {teamId: string; service: IntegrationProviderServiceEnum; event: SlackNotification['event']},
+ {teamId: string; service: IntegrationProviderServiceEnum},
TeamMemberIntegrationAuth[],
string
>(
@@ -174,13 +175,12 @@ export const teamMemberIntegrationAuthsByTeamIdAndEvent = (parent: RootDataLoade
const pg = getKysely()
const res = (await pg
.selectFrom('TeamMemberIntegrationAuth')
- .innerJoin('NotificationSettings', 'authId', 'TeamMemberIntegrationAuth.id')
.selectAll()
.where(({eb, refTuple, tuple}) =>
eb(
- refTuple('teamId', 'service', 'event'),
+ refTuple('teamId', 'service'),
'in',
- keys.map(({teamId, service, event}) => tuple(teamId, service, event))
+ keys.map(({teamId, service}) => tuple(teamId, service))
)
)
.execute()) as unknown as TeamMemberIntegrationAuth[]
@@ -196,20 +196,39 @@ export const teamMemberIntegrationAuthsByTeamIdAndEvent = (parent: RootDataLoade
)
}
-export const notificationSettingsByAuthId = (parent: RootDataLoader) => {
- return new DataLoader(
+export const teamNotificationSettingsByProviderIdAndTeamId = (
+ parent: RootDataLoader,
+ dependsOn: RegisterDependsOn
+) => {
+ dependsOn('teamNotificationSettings')
+ return new DataLoader<
+ {providerId: number; teamId: string},
+ Selectable[],
+ string
+ >(
async (keys) => {
const pg = getKysely()
const res = await pg
- .selectFrom('NotificationSettings')
+ .selectFrom('TeamNotificationSettings')
.selectAll()
- .where(({eb}) => eb('authId', 'in', keys))
+ // convert to text[] as kysely would otherwise not parse the array
+ .select(sql`events::text[]`.as('events'))
+ .where(({eb, refTuple, tuple}) =>
+ eb(
+ refTuple('providerId', 'teamId'),
+ 'in',
+ keys.map(({providerId, teamId}) => tuple(providerId, teamId))
+ )
+ )
.execute()
- return keys.map((key) => res.filter(({authId}) => authId === key).map(({event}) => event))
+ return keys.map((key) =>
+ res.filter(({providerId, teamId}) => providerId === key.providerId && teamId === key.teamId)
+ )
},
{
- ...parent.dataLoaderOptions
+ ...parent.dataLoaderOptions,
+ cacheKeyFn: ({providerId, teamId}) => `${providerId}-${teamId}`
}
)
}
diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts
index c4680eddfd3..26428d85e66 100644
--- a/packages/server/dataloader/primaryKeyLoaderMakers.ts
+++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts
@@ -1,3 +1,4 @@
+import {sql} from 'kysely'
import getKysely from '../postgres/getKysely'
import {getDomainJoinRequestsByIds} from '../postgres/queries/getDomainJoinRequestsByIds'
import getMeetingSeriesByIds from '../postgres/queries/getMeetingSeriesByIds'
@@ -29,6 +30,7 @@ import {
selectTemplateScaleRef,
selectTimelineEvent
} from '../postgres/select'
+import {TeamNotificationSettings} from '../postgres/types/pg'
import {primaryKeyLoaderMaker} from './primaryKeyLoaderMaker'
export const users = primaryKeyLoaderMaker(getUsersByIds)
@@ -164,3 +166,15 @@ export const teamMemberIntegrationAuths = primaryKeyLoaderMaker((ids: readonly n
.where('id', 'in', ids)
.execute()
})
+
+export const teamNotificationSettings = primaryKeyLoaderMaker((ids: readonly number[]) => {
+ return (
+ getKysely()
+ .selectFrom('TeamNotificationSettings')
+ .selectAll()
+ // convert to text[] as kysely would otherwise not parse the array
+ .select(sql`events::text[]`.as('events'))
+ .where('id', 'in', ids)
+ .execute()
+ )
+})
diff --git a/packages/server/graphql/mutations/addIntegrationProvider.ts b/packages/server/graphql/mutations/addIntegrationProvider.ts
deleted file mode 100644
index 9679e0c3541..00000000000
--- a/packages/server/graphql/mutations/addIntegrationProvider.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import {GraphQLNonNull} from 'graphql'
-import {SubscriptionChannel} from 'parabol-client/types/constEnums'
-import {isNotNull} from 'parabol-client/utils/predicates'
-import upsertIntegrationProvider from '../../postgres/queries/upsertIntegrationProvider'
-import {getUserId, isSuperUser, isTeamMember, isUserOrgAdmin} from '../../utils/authorization'
-import publish from '../../utils/publish'
-import {GQLContext} from '../graphql'
-import AddIntegrationProviderInput, {
- IAddIntegrationProviderInput
-} from '../types/AddIntegrationProviderInput'
-import AddIntegrationProviderPayload from '../types/AddIntegrationProviderPayload'
-
-const addIntegrationProvider = {
- name: 'AddIntegrationProvider',
- type: new GraphQLNonNull(AddIntegrationProviderPayload),
- description: 'Adds a new Integration Provider configuration',
- args: {
- input: {
- type: new GraphQLNonNull(AddIntegrationProviderInput),
- description: 'The new Integration Provider'
- }
- },
- resolve: async (
- _source: unknown,
- {
- input
- }: {
- input: IAddIntegrationProviderInput
- },
- context: GQLContext
- ) => {
- const {authToken, dataLoader, socketId: mutatorId} = context
- const {teamId, orgId, scope} = input
- const viewerId = getUserId(authToken)
- const operationId = dataLoader.share()
- const subOptions = {mutatorId, operationId}
-
- // INPUT VALIDATION
- if (scope === 'global' && (teamId || orgId)) {
- return {error: {message: 'Global providers must not have an `orgId` nor `teamId`'}}
- }
- if (scope === 'org' && (!orgId || teamId)) {
- return {error: {message: 'Org providers must have an `orgId` and no `teamId`'}}
- }
- if (scope === 'team' && (!teamId || orgId)) {
- return {error: {message: 'Team providers must have a `teamId` and no `orgId`'}}
- }
-
- // AUTH
- if (!isSuperUser(authToken)) {
- if (scope === 'global') {
- return {error: {message: 'Must be a super user to add a global provider'}}
- }
- if (scope === 'org' && !(await isUserOrgAdmin(viewerId, orgId!, dataLoader))) {
- return {
- error: {
- message:
- 'Must be an organization admin to add an integration provider on organization level'
- }
- }
- }
- if (scope === 'team' && !isTeamMember(authToken, teamId!)) {
- return {error: {message: 'Must be on the team for the integration provider'}}
- }
- }
-
- // VALIDATION
- const {
- authStrategy,
- oAuth1ProviderMetadataInput,
- oAuth2ProviderMetadataInput,
- webhookProviderMetadataInput,
- sharedSecretMetadataInput,
- ...rest
- } = input
-
- if (authStrategy === 'oauth1' && !oAuth1ProviderMetadataInput) {
- return {error: {message: 'Auth strategy oauth1 requires oAuth1ProviderMetadataInput'}}
- }
- if (authStrategy === 'oauth2' && !oAuth2ProviderMetadataInput) {
- return {error: {message: 'Auth strategy oauth2 requires oAuth2ProviderMetadataInput'}}
- }
- if (authStrategy === 'webhook' && !webhookProviderMetadataInput) {
- return {error: {message: 'Auth strategy webhook requires webhookProviderMetadataInput'}}
- }
- if (
- [
- oAuth1ProviderMetadataInput,
- oAuth2ProviderMetadataInput,
- webhookProviderMetadataInput,
- sharedSecretMetadataInput
- ].filter(isNotNull).length !== 1
- ) {
- return {error: {message: 'Exactly 1 metadata provider is expected'}}
- }
-
- // RESOLUTION
- const providerId = await upsertIntegrationProvider({
- authStrategy,
- ...rest,
- ...oAuth1ProviderMetadataInput,
- ...oAuth2ProviderMetadataInput,
- ...webhookProviderMetadataInput,
- ...sharedSecretMetadataInput,
- ...(scope === 'global'
- ? {orgId: null, teamId: null}
- : scope === 'org'
- ? {orgId, teamId: null}
- : {orgId: null, teamId})
- })
-
- const data = {
- providerId,
- orgId,
- teamId
- }
- if (orgId) {
- publish(
- SubscriptionChannel.ORGANIZATION,
- orgId,
- 'AddIntegrationProviderSuccess',
- data,
- subOptions
- )
- }
- return data
- }
-}
-
-export default addIntegrationProvider
diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts
index 3172da054f8..917cd27fa0b 100644
--- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts
+++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts
@@ -7,11 +7,12 @@ import {IntegrationProviderMSTeams as IIntegrationProviderMSTeams} from '../../.
import {SlackNotification, Team} from '../../../../postgres/types'
import IUser from '../../../../postgres/types/IUser'
import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting'
-import {NotificationSettings} from '../../../../postgres/types/pg'
import MSTeamsServerManager from '../../../../utils/MSTeamsServerManager'
import {analytics} from '../../../../utils/analytics/analytics'
import sendToSentry from '../../../../utils/sendToSentry'
import {DataLoaderWorker} from '../../../graphql'
+import isValid from '../../../isValid'
+import {SlackNotificationEventEnum} from '../../../public/resolverTypes'
import {NotificationIntegrationHelper} from './NotificationIntegrationHelper'
import {createNotifier} from './Notifier'
import getSummaryText from './getSummaryText'
@@ -338,22 +339,39 @@ async function getMSTeams(
dataLoader: DataLoaderWorker,
teamId: string,
userId: string,
- event: NotificationSettings['event']
+ event: SlackNotificationEventEnum
) {
const [auths, user] = await Promise.all([
dataLoader
- .get('teamMemberIntegrationAuthsByTeamIdAndEvent')
- .load({service: 'msTeams', teamId, event}),
+ .get('teamMemberIntegrationAuthsByTeamIdAndService')
+ .load({service: 'msTeams', teamId}),
dataLoader.get('users').loadNonNull(userId)
])
- return Promise.all(
- auths.map(async (auth) => {
- const provider = await dataLoader.get('integrationProviders').loadNonNull(auth.providerId)
- return MSTeamsNotificationHelper({
- ...(provider as IntegrationProviderMSTeams),
- userId,
- email: user.email
+
+ const providers = (
+ await Promise.all(
+ auths.map(async (auth) => {
+ const {providerId} = auth
+ const [provider, settings] = await Promise.all([
+ dataLoader
+ .get('integrationProviders')
+ .loadNonNull(providerId) as Promise,
+ dataLoader.get('teamNotificationSettingsByProviderIdAndTeamId').load({providerId, teamId})
+ ])
+ const activeSettings = settings.find(({channelId}) => channelId === null)
+ if (activeSettings?.events.includes(event)) {
+ return provider
+ }
+ return null
})
+ )
+ ).filter(isValid)
+
+ return providers.map((provider) =>
+ MSTeamsNotificationHelper({
+ ...(provider as IntegrationProviderMSTeams),
+ userId,
+ email: user.email
})
)
}
diff --git a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts
index 7bbe29a2965..7519494d7fc 100644
--- a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts
+++ b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts
@@ -8,12 +8,13 @@ import {IntegrationProviderMattermost} from '../../../../postgres/queries/getInt
import {SlackNotification, Team} from '../../../../postgres/types'
import IUser from '../../../../postgres/types/IUser'
import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting'
-import {NotificationSettings} from '../../../../postgres/types/pg'
import MattermostServerManager from '../../../../utils/MattermostServerManager'
import {analytics} from '../../../../utils/analytics/analytics'
import {toEpochSeconds} from '../../../../utils/epochTime'
import sendToSentry from '../../../../utils/sendToSentry'
import {DataLoaderWorker} from '../../../graphql'
+import isValid from '../../../isValid'
+import {SlackNotificationEventEnum} from '../../../public/resolverTypes'
import {NotificationIntegrationHelper} from './NotificationIntegrationHelper'
import {createNotifier} from './Notifier'
import getSummaryText from './getSummaryText'
@@ -359,7 +360,7 @@ async function getMattermost(
dataLoader: DataLoaderWorker,
teamId: string,
userId: string,
- event: NotificationSettings['event']
+ event: SlackNotificationEventEnum
) {
if (MATTERMOST_SECRET && MATTERMOST_URL) {
return [
@@ -374,17 +375,29 @@ async function getMattermost(
}
const auths = await dataLoader
- .get('teamMemberIntegrationAuthsByTeamIdAndEvent')
- .load({service: 'mattermost', teamId, event})
+ .get('teamMemberIntegrationAuthsByTeamIdAndService')
+ .load({service: 'mattermost', teamId})
- return Promise.all(
- auths.map(async (auth) => {
- const provider = (await dataLoader
- .get('integrationProviders')
- .loadNonNull(auth.providerId)) as IntegrationProviderMattermost
- return MattermostNotificationHelper({...provider, teamId, userId})
- })
- )
+ const providers = (
+ await Promise.all(
+ auths.map(async (auth) => {
+ const {providerId} = auth
+ const [provider, settings] = await Promise.all([
+ dataLoader
+ .get('integrationProviders')
+ .loadNonNull(providerId) as Promise,
+ dataLoader.get('teamNotificationSettingsByProviderIdAndTeamId').load({providerId, teamId})
+ ])
+ const activeSettings = settings.find(({channelId}) => channelId === null)
+ if (activeSettings?.events.includes(event)) {
+ return provider
+ }
+ return null
+ })
+ )
+ ).filter(isValid)
+
+ return providers.map((provider) => MattermostNotificationHelper({...provider, teamId, userId}))
}
export const MattermostNotifier = createNotifier(getMattermost)
diff --git a/packages/server/graphql/public/mutations/addIntegrationProvider.ts b/packages/server/graphql/public/mutations/addIntegrationProvider.ts
new file mode 100644
index 00000000000..041ca93d0a1
--- /dev/null
+++ b/packages/server/graphql/public/mutations/addIntegrationProvider.ts
@@ -0,0 +1,114 @@
+import {SubscriptionChannel} from 'parabol-client/types/constEnums'
+import {isNotNull} from 'parabol-client/utils/predicates'
+import upsertIntegrationProvider from '../../../postgres/queries/upsertIntegrationProvider'
+import {getUserId, isSuperUser, isTeamMember, isUserOrgAdmin} from '../../../utils/authorization'
+import publish from '../../../utils/publish'
+import {MutationResolvers} from '../resolverTypes'
+
+const addIntegrationProvider: MutationResolvers['addIntegrationProvider'] = async (
+ _source,
+ {input},
+ context
+) => {
+ const {authToken, dataLoader, socketId: mutatorId} = context
+ const {teamId, orgId, scope, service} = input
+ const viewerId = getUserId(authToken)
+ const operationId = dataLoader.share()
+ const subOptions = {mutatorId, operationId}
+
+ // INPUT VALIDATION
+ if (service === 'jira' || service === 'github') {
+ return {error: {message: 'Service is not supported'}}
+ }
+ if (scope === 'global' && (teamId || orgId)) {
+ return {error: {message: 'Global providers must not have an `orgId` nor `teamId`'}}
+ }
+ if (scope === 'org' && (!orgId || teamId)) {
+ return {error: {message: 'Org providers must have an `orgId` and no `teamId`'}}
+ }
+ if (scope === 'team' && (!teamId || orgId)) {
+ return {error: {message: 'Team providers must have a `teamId` and no `orgId`'}}
+ }
+
+ // AUTH
+ if (!isSuperUser(authToken)) {
+ if (scope === 'global') {
+ return {error: {message: 'Must be a super user to add a global provider'}}
+ }
+ if (scope === 'org' && !(await isUserOrgAdmin(viewerId, orgId!, dataLoader))) {
+ return {
+ error: {
+ message:
+ 'Must be an organization admin to add an integration provider on organization level'
+ }
+ }
+ }
+ if (scope === 'team' && !isTeamMember(authToken, teamId!)) {
+ return {error: {message: 'Must be on the team for the integration provider'}}
+ }
+ }
+
+ // VALIDATION
+ const {
+ authStrategy,
+ oAuth1ProviderMetadataInput,
+ oAuth2ProviderMetadataInput,
+ webhookProviderMetadataInput,
+ sharedSecretMetadataInput,
+ ...rest
+ } = input
+
+ if (authStrategy === 'oauth1' && !oAuth1ProviderMetadataInput) {
+ return {error: {message: 'Auth strategy oauth1 requires oAuth1ProviderMetadataInput'}}
+ }
+ if (authStrategy === 'oauth2' && !oAuth2ProviderMetadataInput) {
+ return {error: {message: 'Auth strategy oauth2 requires oAuth2ProviderMetadataInput'}}
+ }
+ if (authStrategy === 'webhook' && !webhookProviderMetadataInput) {
+ return {error: {message: 'Auth strategy webhook requires webhookProviderMetadataInput'}}
+ }
+ if (
+ [
+ oAuth1ProviderMetadataInput,
+ oAuth2ProviderMetadataInput,
+ webhookProviderMetadataInput,
+ sharedSecretMetadataInput
+ ].filter(isNotNull).length !== 1
+ ) {
+ return {error: {message: 'Exactly 1 metadata provider is expected'}}
+ }
+
+ // RESOLUTION
+ const providerId = await upsertIntegrationProvider({
+ authStrategy,
+ ...rest,
+ service,
+ ...oAuth1ProviderMetadataInput,
+ ...oAuth2ProviderMetadataInput,
+ ...webhookProviderMetadataInput,
+ ...sharedSecretMetadataInput,
+ ...(scope === 'global'
+ ? {orgId: null, teamId: null}
+ : scope === 'org'
+ ? {orgId: orgId!, teamId: null}
+ : {orgId: null, teamId: teamId!})
+ })
+
+ const data = {
+ providerId,
+ orgId: orgId || undefined,
+ teamId: teamId || undefined
+ }
+ if (orgId) {
+ publish(
+ SubscriptionChannel.ORGANIZATION,
+ orgId,
+ 'AddIntegrationProviderSuccess',
+ data,
+ subOptions
+ )
+ }
+ return data
+}
+
+export default addIntegrationProvider
diff --git a/packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts b/packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts
index bf5fa254250..b4578c2a85e 100644
--- a/packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts
+++ b/packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts
@@ -149,15 +149,18 @@ const addTeamMemberIntegrationAuth: MutationResolvers['addTeamMemberIntegrationA
})
}
- await pg
- .insertInto('NotificationSettings')
- .columns(['authId', 'event'])
- .values(() => ({
- authId,
- event: sql`unnest(enum_range(NULL::"SlackNotificationEventEnum"))`
- }))
- .onConflict((oc) => oc.doNothing())
- .execute()
+ if (service === 'msTeams' || service === 'mattermost') {
+ await pg
+ .insertInto('TeamNotificationSettings')
+ .columns(['providerId', 'teamId', 'events'])
+ .values(() => ({
+ providerId: providerDbId,
+ teamId,
+ events: sql`enum_range(NULL::"SlackNotificationEventEnum")`
+ }))
+ .onConflict((oc) => oc.doNothing())
+ .execute()
+ }
updateRepoIntegrationsCacheByPerms(dataLoader, viewerId, teamId, true)
diff --git a/packages/server/graphql/public/mutations/setNotificationSetting.ts b/packages/server/graphql/public/mutations/setNotificationSetting.ts
deleted file mode 100644
index cd79ed2a4b4..00000000000
--- a/packages/server/graphql/public/mutations/setNotificationSetting.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import {SubscriptionChannel} from 'parabol-client/types/constEnums'
-import TeamMemberIntegrationAuthId from '../../../../client/shared/gqlIds/TeamMemberIntegrationAuthId'
-import getKysely from '../../../postgres/getKysely'
-import {getUserId, isTeamMember} from '../../../utils/authorization'
-import publish from '../../../utils/publish'
-import standardError from '../../../utils/standardError'
-import {MutationResolvers} from '../resolverTypes'
-
-const setNotificationSetting: MutationResolvers['setNotificationSetting'] = async (
- _source,
- {authId: gqlAuthId, event, isEnabled},
- {authToken, dataLoader, socketId: mutatorId}
-) => {
- const viewerId = getUserId(authToken)
- const operationId = dataLoader.share()
- const subOptions = {mutatorId, operationId}
- const pg = getKysely()
-
- // AUTH
- const authId = TeamMemberIntegrationAuthId.split(gqlAuthId)
- const auth = await dataLoader.get('teamMemberIntegrationAuths').load(authId)
- if (!auth) {
- return standardError(new Error('Integration auth not found'), {userId: viewerId})
- }
- const {teamId, service} = auth
- if (!isTeamMember(authToken, teamId)) {
- return standardError(new Error('Attempted teamId spoof'), {userId: viewerId})
- }
-
- // VALIDATION
- if (service !== 'mattermost' && service !== 'msTeams') {
- return standardError(new Error('Invalid integration provider'), {userId: viewerId})
- }
-
- // RESOLUTION
- if (isEnabled) {
- await pg
- .insertInto('NotificationSettings')
- .values({authId, event})
- .onConflict((oc) => oc.doNothing())
- .execute()
- } else {
- await pg
- .deleteFrom('NotificationSettings')
- .where('authId', '=', authId)
- .where('event', '=', event)
- .execute()
- }
- const data = {authId}
- publish(SubscriptionChannel.TEAM, teamId, 'SetNotificationSettingSuccess', data, subOptions)
- return data
-}
-
-export default setNotificationSetting
diff --git a/packages/server/graphql/public/mutations/setTeamNotificationSetting.ts b/packages/server/graphql/public/mutations/setTeamNotificationSetting.ts
new file mode 100644
index 00000000000..55d82031bd1
--- /dev/null
+++ b/packages/server/graphql/public/mutations/setTeamNotificationSetting.ts
@@ -0,0 +1,58 @@
+import {SubscriptionChannel} from 'parabol-client/types/constEnums'
+import TeamNotificationSettingsId from '../../../../client/shared/gqlIds/TeamNotificationSettingsId'
+import getKysely from '../../../postgres/getKysely'
+import {getUserId, isTeamMember} from '../../../utils/authorization'
+import publish from '../../../utils/publish'
+import standardError from '../../../utils/standardError'
+import {MutationResolvers} from '../resolverTypes'
+
+const setTeamNotificationSetting: MutationResolvers['setTeamNotificationSetting'] = async (
+ _source,
+ {id: gqlId, event, isEnabled},
+ {authToken, dataLoader, socketId: mutatorId}
+) => {
+ const viewerId = getUserId(authToken)
+ const operationId = dataLoader.share()
+ const subOptions = {mutatorId, operationId}
+ const pg = getKysely()
+ const id = TeamNotificationSettingsId.split(gqlId)
+
+ // AUTH
+ const setting = await dataLoader.get('teamNotificationSettings').load(id)
+ if (!setting) {
+ return standardError(new Error('TeamNotificationSetting not found'), {userId: viewerId})
+ }
+ const {teamId} = setting
+ if (!isTeamMember(authToken, teamId)) {
+ return standardError(new Error('Attempted teamId spoof'), {userId: viewerId})
+ }
+
+ // RESOLUTION
+ if (isEnabled) {
+ await pg
+ .updateTable('TeamNotificationSettings')
+ .set(({fn, val}) => ({
+ events: fn('arr_append_uniq', ['events', val(event)])
+ }))
+ .where('id', '=', id)
+ .execute()
+ } else {
+ await pg
+ .updateTable('TeamNotificationSettings')
+ .set(({fn, val}) => ({
+ events: fn('array_remove', ['events', val(event)])
+ }))
+ .where('id', '=', id)
+ .execute()
+ }
+ // update dataLoader
+ setting.events = isEnabled
+ ? [...setting.events, event]
+ : setting.events.filter((e: any) => e !== event)
+
+ const data = {id}
+ publish(SubscriptionChannel.TEAM, teamId, 'SetNotificationSettingSuccess', data, subOptions)
+ return data
+}
+
+export default setTeamNotificationSetting
diff --git a/packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql b/packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql
index 51ef034678a..8cf853b2101 100644
--- a/packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql
+++ b/packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql
@@ -11,4 +11,19 @@ type MSTeamsIntegration {
The non-global providers shared with the team or organization
"""
sharedProviders: [IntegrationProviderWebhook!]!
+
+ """
+ An active team member has integrated with a provider for this integration
+ """
+ isActive: Boolean!
+
+ """
+ If any team member integrated with mattermost, this will be the active provider for this team
+ """
+ activeProvider: IntegrationProviderWebhook
+
+ """
+ Team notification settings for this provider.
+ """
+ teamNotificationSettings(channel: ID): TeamNotificationSettings
}
diff --git a/packages/server/graphql/public/typeDefs/MattermostIntegration.graphql b/packages/server/graphql/public/typeDefs/MattermostIntegration.graphql
index e072cb55448..bdff353f27a 100644
--- a/packages/server/graphql/public/typeDefs/MattermostIntegration.graphql
+++ b/packages/server/graphql/public/typeDefs/MattermostIntegration.graphql
@@ -11,4 +11,19 @@ type MattermostIntegration {
The non-global providers shared with the team or organization
"""
sharedProviders: [IntegrationProviderWebhook!]!
+
+ """
+ An active team member has integrated with a provider for this integration
+ """
+ isActive: Boolean!
+
+ """
+ If any team member integrated with mattermost, this will be the active provider for this team
+ """
+ activeProvider: IntegrationProviderWebhook
+
+ """
+ Team notification settings for this provider.
+ """
+ teamNotificationSettings(channel: ID): TeamNotificationSettings
}
diff --git a/packages/server/graphql/public/typeDefs/Mutation.graphql b/packages/server/graphql/public/typeDefs/Mutation.graphql
index ea0a7b472b5..adbd1df3cd5 100644
--- a/packages/server/graphql/public/typeDefs/Mutation.graphql
+++ b/packages/server/graphql/public/typeDefs/Mutation.graphql
@@ -802,20 +802,20 @@ type Mutation {
): SetSlackNotificationPayload!
"""
- Set the notification settings for a provider and team
+ Update the notification settings for a provider and team
"""
- setNotificationSetting(
+ setTeamNotificationSetting(
"""
- ID of the TeamMemberIntegrationAuth for which to set the notification setting
+ The unique id for the setting
"""
- authId: ID!
+ id: ID!
"""
Event type to modify
"""
event: SlackNotificationEventEnum!
isEnabled: Boolean!
- ): SetNotificationSettingPayload!
+ ): SetTeamNotificationSettingPayload!
"""
Broadcast that the viewer started dragging a reflection
diff --git a/packages/server/graphql/public/typeDefs/SetNotificationSettingPayload.graphql b/packages/server/graphql/public/typeDefs/SetNotificationSettingPayload.graphql
deleted file mode 100644
index 5c691a798c0..00000000000
--- a/packages/server/graphql/public/typeDefs/SetNotificationSettingPayload.graphql
+++ /dev/null
@@ -1,4 +0,0 @@
-"""
-Return for setNotificationSetting mutation
-"""
-union SetNotificationSettingPayload = ErrorPayload | SetNotificationSettingSuccess
diff --git a/packages/server/graphql/public/typeDefs/SetNotificationSettingSuccess.graphql b/packages/server/graphql/public/typeDefs/SetNotificationSettingSuccess.graphql
deleted file mode 100644
index 7399371f82b..00000000000
--- a/packages/server/graphql/public/typeDefs/SetNotificationSettingSuccess.graphql
+++ /dev/null
@@ -1,14 +0,0 @@
-type SetNotificationSettingSuccess {
- authId: ID!
-
- """
- The updated auth object
- For now this is only implemented for webhook
- """
- auth: TeamMemberIntegrationAuthWebhook!
-
- """
- Enabled events for this provider and team
- """
- events: [SlackNotificationEventEnum!]!
-}
diff --git a/packages/server/graphql/public/typeDefs/SetTeamNotificationSettingPayload.graphql b/packages/server/graphql/public/typeDefs/SetTeamNotificationSettingPayload.graphql
new file mode 100644
index 00000000000..272579e49fd
--- /dev/null
+++ b/packages/server/graphql/public/typeDefs/SetTeamNotificationSettingPayload.graphql
@@ -0,0 +1,4 @@
+"""
+Return for setTeamNotificationSetting mutation
+"""
+union SetTeamNotificationSettingPayload = ErrorPayload | SetTeamNotificationSettingSuccess
diff --git a/packages/server/graphql/public/typeDefs/SetTeamNotificationSettingSuccess.graphql b/packages/server/graphql/public/typeDefs/SetTeamNotificationSettingSuccess.graphql
new file mode 100644
index 00000000000..b83378886b2
--- /dev/null
+++ b/packages/server/graphql/public/typeDefs/SetTeamNotificationSettingSuccess.graphql
@@ -0,0 +1,6 @@
+type SetTeamNotificationSettingSuccess {
+ """
+ The updated settings
+ """
+ teamNotificationSettings: TeamNotificationSettings!
+}
diff --git a/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql
index 17bac55ca03..aa0bf1c0bad 100644
--- a/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql
+++ b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql
@@ -41,9 +41,4 @@ type TeamMemberIntegrationAuthWebhook implements TeamMemberIntegrationAuth {
The provider strategy this token connects to
"""
provider: IntegrationProviderWebhook!
-
- """
- The events that trigger a notification
- """
- events: [SlackNotificationEventEnum!]!
}
diff --git a/packages/server/graphql/public/typeDefs/TeamNotificationSettings.graphql b/packages/server/graphql/public/typeDefs/TeamNotificationSettings.graphql
new file mode 100644
index 00000000000..cf6be0a5802
--- /dev/null
+++ b/packages/server/graphql/public/typeDefs/TeamNotificationSettings.graphql
@@ -0,0 +1,19 @@
+"""
+Notification settings for a team. All team members
+"""
+type TeamNotificationSettings {
+ id: ID!
+ teamId: ID!
+ providerId: ID!
+
+ """
+ The channel in the integration to wich these settings apply.
+ Null for the default channel.
+ """
+ channel: ID
+
+ """
+ The events that trigger a notification
+ """
+ events: [SlackNotificationEventEnum!]!
+}
diff --git a/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql b/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql
index 07648a0bb0d..3b2f0f46483 100644
--- a/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql
+++ b/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql
@@ -22,7 +22,7 @@ type TeamSubscriptionPayload {
RemoveTeamMemberPayload: RemoveTeamMemberPayload
RenameMeetingSuccess: RenameMeetingSuccess
SelectTemplatePayload: SelectTemplatePayload
- SetNotificationSettingSuccess: SetNotificationSettingSuccess
+ SetTeamNotificationSettingSuccess: SetTeamNotificationSettingSuccess
StartCheckInSuccess: StartCheckInSuccess
StartRetrospectiveSuccess: StartRetrospectiveSuccess
StartSprintPokerSuccess: StartSprintPokerSuccess
diff --git a/packages/server/graphql/public/types/IntegrationProvider.ts b/packages/server/graphql/public/types/IntegrationProvider.ts
new file mode 100644
index 00000000000..9d229b94205
--- /dev/null
+++ b/packages/server/graphql/public/types/IntegrationProvider.ts
@@ -0,0 +1,11 @@
+import IntegrationProviderId from '../../../../client/shared/gqlIds/IntegrationProviderId'
+import {TIntegrationProvider} from '../../../postgres/queries/getIntegrationProvidersByIds'
+import {IntegrationProviderResolvers} from '../resolverTypes'
+
+export type IntegrationProviderSource = TIntegrationProvider
+
+const IntegrationProvider: IntegrationProviderResolvers = {
+ id: ({id}) => IntegrationProviderId.join(id)
+}
+
+export default IntegrationProvider
diff --git a/packages/server/graphql/public/types/IntegrationProviderOAuth1.ts b/packages/server/graphql/public/types/IntegrationProviderOAuth1.ts
new file mode 100644
index 00000000000..3eda033704a
--- /dev/null
+++ b/packages/server/graphql/public/types/IntegrationProviderOAuth1.ts
@@ -0,0 +1,12 @@
+import {TIntegrationProvider} from '../../../postgres/queries/getIntegrationProvidersByIds'
+import {IntegrationProviderOAuth1Resolvers} from '../resolverTypes'
+import IntegrationProvider from './IntegrationProvider'
+
+export type IntegrationProviderOAuth1Source = TIntegrationProvider
+
+const IntegrationProviderOAuth1: IntegrationProviderOAuth1Resolvers = {
+ ...IntegrationProvider,
+ __isTypeOf: ({authStrategy}) => authStrategy === 'oauth1'
+}
+
+export default IntegrationProviderOAuth1
diff --git a/packages/server/graphql/public/types/IntegrationProviderOAuth2.ts b/packages/server/graphql/public/types/IntegrationProviderOAuth2.ts
new file mode 100644
index 00000000000..091512b46d6
--- /dev/null
+++ b/packages/server/graphql/public/types/IntegrationProviderOAuth2.ts
@@ -0,0 +1,12 @@
+import {TIntegrationProvider} from '../../../postgres/queries/getIntegrationProvidersByIds'
+import {IntegrationProviderOAuth2Resolvers} from '../resolverTypes'
+import IntegrationProvider from './IntegrationProvider'
+
+export type IntegrationProviderOAuth2Source = TIntegrationProvider
+
+const IntegrationProviderOAuth2: IntegrationProviderOAuth2Resolvers = {
+ ...IntegrationProvider,
+ __isTypeOf: ({authStrategy}) => authStrategy === 'oauth2'
+}
+
+export default IntegrationProviderOAuth2
diff --git a/packages/server/graphql/public/types/IntegrationProviderWebhook.ts b/packages/server/graphql/public/types/IntegrationProviderWebhook.ts
new file mode 100644
index 00000000000..c4b3d59674d
--- /dev/null
+++ b/packages/server/graphql/public/types/IntegrationProviderWebhook.ts
@@ -0,0 +1,12 @@
+import {TIntegrationProvider} from '../../../postgres/queries/getIntegrationProvidersByIds'
+import {IntegrationProviderWebhookResolvers} from '../resolverTypes'
+import IntegrationProvider from './IntegrationProvider'
+
+export type IntegrationProviderWebhookSource = TIntegrationProvider
+
+const IntegrationProviderWebhook: IntegrationProviderWebhookResolvers = {
+ ...IntegrationProvider,
+ __isTypeOf: ({authStrategy}) => authStrategy === 'webhook'
+}
+
+export default IntegrationProviderWebhook
diff --git a/packages/server/graphql/public/types/MSTeamsIntegration.ts b/packages/server/graphql/public/types/MSTeamsIntegration.ts
index 9b434ed29fe..78ceae203f2 100644
--- a/packages/server/graphql/public/types/MSTeamsIntegration.ts
+++ b/packages/server/graphql/public/types/MSTeamsIntegration.ts
@@ -1,3 +1,4 @@
+import {DataLoaderWorker} from '../../graphql'
import {MsTeamsIntegrationResolvers} from '../resolverTypes'
export type MSTeamsIntegrationSource = {
@@ -5,6 +6,15 @@ export type MSTeamsIntegrationSource = {
userId: string
}
+const loadActiveProvider = async (teamId: string, dataLoader: DataLoaderWorker) => {
+ const auths = await dataLoader
+ .get('teamMemberIntegrationAuthsByTeamIdAndService')
+ .load({teamId, service: 'mattermost'})
+ if (!auths || auths.length !== 1) return null
+ const {providerId} = auths[0]!
+ return await dataLoader.get('integrationProviders').loadNonNull(providerId)
+}
+
const MSTeamsIntegration: MsTeamsIntegrationResolvers = {
auth: async ({teamId, userId}, _args, {dataLoader}) => {
return dataLoader
@@ -18,6 +28,27 @@ const MSTeamsIntegration: MsTeamsIntegrationResolvers = {
return dataLoader
.get('sharedIntegrationProviders')
.load({service: 'msTeams', orgIds: [orgId], teamIds: [teamId]})
+ },
+
+ isActive: async ({teamId}, _args, {dataLoader}) => {
+ const auths = await dataLoader
+ .get('teamMemberIntegrationAuthsByTeamIdAndService')
+ .load({teamId, service: 'mattermost'})
+ return auths && auths.length > 1
+ },
+
+ activeProvider: async ({teamId}, _args, {dataLoader}) => {
+ return loadActiveProvider(teamId, dataLoader)
+ },
+
+ teamNotificationSettings: async ({teamId}, {channel}, {dataLoader}) => {
+ const activeProvider = await loadActiveProvider(teamId, dataLoader)
+ if (!activeProvider) return null
+ const {id} = activeProvider
+ const settings = await dataLoader
+ .get('teamNotificationSettingsByProviderIdAndTeamId')
+ .load({providerId: id, teamId})
+ return settings.find(({channelId}) => (!channelId && !channel) || channelId === channel) || null
}
}
diff --git a/packages/server/graphql/public/types/MattermostIntegration.ts b/packages/server/graphql/public/types/MattermostIntegration.ts
index 9bbc3521e9a..a0c2269f471 100644
--- a/packages/server/graphql/public/types/MattermostIntegration.ts
+++ b/packages/server/graphql/public/types/MattermostIntegration.ts
@@ -1,3 +1,4 @@
+import {DataLoaderWorker} from '../../graphql'
import {MattermostIntegrationResolvers} from '../resolverTypes'
export type MattermostIntegrationSource = {
@@ -5,6 +6,15 @@ export type MattermostIntegrationSource = {
userId: string
}
+const loadActiveProvider = async (teamId: string, dataLoader: DataLoaderWorker) => {
+ const auths = await dataLoader
+ .get('teamMemberIntegrationAuthsByTeamIdAndService')
+ .load({teamId, service: 'mattermost'})
+ if (!auths || auths.length !== 1) return null
+ const {providerId} = auths[0]!
+ return await dataLoader.get('integrationProviders').loadNonNull(providerId)
+}
+
const MattermostIntegration: MattermostIntegrationResolvers = {
auth: async ({teamId, userId}, _args, {dataLoader}) => {
const res = await dataLoader
@@ -19,6 +29,27 @@ const MattermostIntegration: MattermostIntegrationResolvers = {
return dataLoader
.get('sharedIntegrationProviders')
.load({service: 'mattermost', orgIds: [orgId], teamIds: [teamId]})
+ },
+
+ isActive: async ({teamId}, _args, {dataLoader}) => {
+ const auths = await dataLoader
+ .get('teamMemberIntegrationAuthsByTeamIdAndService')
+ .load({teamId, service: 'mattermost'})
+ return auths && auths.length > 1
+ },
+
+ activeProvider: async ({teamId}, _args, {dataLoader}) => {
+ return loadActiveProvider(teamId, dataLoader)
+ },
+
+ teamNotificationSettings: async ({teamId}, {channel}, {dataLoader}) => {
+ const activeProvider = await loadActiveProvider(teamId, dataLoader)
+ if (!activeProvider) return null
+ const {id} = activeProvider
+ const settings = await dataLoader
+ .get('teamNotificationSettingsByProviderIdAndTeamId')
+ .load({providerId: id, teamId})
+ return settings.find(({channelId}) => (!channelId && !channel) || channelId === channel) || null
}
}
diff --git a/packages/server/graphql/public/types/SetNotificationSettingSuccess.ts b/packages/server/graphql/public/types/SetNotificationSettingSuccess.ts
deleted file mode 100644
index c799bac1cc0..00000000000
--- a/packages/server/graphql/public/types/SetNotificationSettingSuccess.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {SetNotificationSettingSuccessResolvers} from '../resolverTypes'
-
-export type SetNotificationSettingSuccessSource = {
- authId: number
-}
-
-const SetNotificationSettingSuccess: SetNotificationSettingSuccessResolvers = {
- auth: async ({authId}, _args, {dataLoader}) => {
- return dataLoader.get('teamMemberIntegrationAuths').loadNonNull(authId)
- },
- events: async ({authId}, _args, {dataLoader}) => {
- return dataLoader.get('notificationSettingsByAuthId').load(authId)
- }
-}
-
-export default SetNotificationSettingSuccess
diff --git a/packages/server/graphql/public/types/SetTeamNotificationSettingSuccess.ts b/packages/server/graphql/public/types/SetTeamNotificationSettingSuccess.ts
new file mode 100644
index 00000000000..5ba4887b016
--- /dev/null
+++ b/packages/server/graphql/public/types/SetTeamNotificationSettingSuccess.ts
@@ -0,0 +1,16 @@
+import {SetTeamNotificationSettingSuccessResolvers} from '../resolverTypes'
+
+export type SetTeamNotificationSettingSuccessSource = {
+ // db id
+ id: number
+}
+
+const SetTeamNotificationSettingSuccess: SetTeamNotificationSettingSuccessResolvers = {
+ teamNotificationSettings: async (source, _args, {dataLoader}) => {
+ const {id} = source
+ const settings = await dataLoader.get('teamNotificationSettings').loadNonNull(id)
+ return settings
+ }
+}
+
+export default SetTeamNotificationSettingSuccess
diff --git a/packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts b/packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts
index fe7b1e72ab5..e0246a384d5 100644
--- a/packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts
+++ b/packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts
@@ -8,9 +8,6 @@ const TeamMemberIntegrationAuthWebhook: TeamMemberIntegrationAuthWebhookResolver
providerId: ({providerId}) => IntegrationProviderId.join(providerId),
provider: async ({providerId}, _args, {dataLoader}) => {
return dataLoader.get('integrationProviders').loadNonNull(providerId)
- },
- events: async ({id}, _args, {dataLoader}) => {
- return dataLoader.get('notificationSettingsByAuthId').load(id)
}
}
diff --git a/packages/server/graphql/public/types/TeamNotificationSettings.ts b/packages/server/graphql/public/types/TeamNotificationSettings.ts
new file mode 100644
index 00000000000..7b6bb2ea997
--- /dev/null
+++ b/packages/server/graphql/public/types/TeamNotificationSettings.ts
@@ -0,0 +1,12 @@
+import {Selectable} from 'kysely'
+import TeamNotificationSettingsId from '../../../../client/shared/gqlIds/TeamNotificationSettingsId'
+import {TeamNotificationSettings as TeamNotificationSettingsDB} from '../../../postgres/types/pg'
+import {TeamNotificationSettingsResolvers} from '../resolverTypes'
+
+export type TeamNotificationSettingsSource = Selectable
+
+const TeamNotificationSettings: TeamNotificationSettingsResolvers = {
+ id: ({id}) => TeamNotificationSettingsId.join(id)
+}
+
+export default TeamNotificationSettings
diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts
index 4a5aa57d749..5b457bf14ee 100644
--- a/packages/server/graphql/rootMutation.ts
+++ b/packages/server/graphql/rootMutation.ts
@@ -2,7 +2,6 @@ import {GraphQLObjectType} from 'graphql'
import {GQLContext} from './graphql'
import addAtlassianAuth from './mutations/addAtlassianAuth'
import addGitHubAuth from './mutations/addGitHubAuth'
-import addIntegrationProvider from './mutations/addIntegrationProvider'
import addOrg from './mutations/addOrg'
import addPokerTemplateDimension from './mutations/addPokerTemplateDimension'
import addPokerTemplateScale from './mutations/addPokerTemplateScale'
@@ -192,7 +191,6 @@ export default new GraphQLObjectType({
toggleTeamDrawer,
updateGitHubDimensionField,
createPoll,
- addIntegrationProvider,
removeIntegrationProvider,
updateAzureDevOpsDimensionField
}) as any
diff --git a/packages/server/graphql/rootTypes.ts b/packages/server/graphql/rootTypes.ts
index e9294b3f48c..b974f7f054d 100644
--- a/packages/server/graphql/rootTypes.ts
+++ b/packages/server/graphql/rootTypes.ts
@@ -1,7 +1,3 @@
-import IntegrationProviderOAuth1 from './types/IntegrationProviderOAuth1'
-import IntegrationProviderOAuth2 from './types/IntegrationProviderOAuth2'
-import IntegrationProviderSharedSecret from './types/IntegrationProviderSharedSecret'
-import IntegrationProviderWebhook from './types/IntegrationProviderWebhook'
import JiraDimensionField from './types/JiraDimensionField'
import RenamePokerTemplatePayload from './types/RenamePokerTemplatePayload'
import SetMeetingSettingsPayload from './types/SetMeetingSettingsPayload'
@@ -13,10 +9,6 @@ import TimelineEventTeamCreated from './types/TimelineEventTeamCreated'
import UserTiersCount from './types/UserTiersCount'
const rootTypes = [
- IntegrationProviderOAuth1,
- IntegrationProviderOAuth2,
- IntegrationProviderSharedSecret,
- IntegrationProviderWebhook,
SetMeetingSettingsPayload,
TimelineEventTeamCreated,
TimelineEventJoinedParabol,
diff --git a/packages/server/graphql/types/AddIntegrationProviderPayload.ts b/packages/server/graphql/types/AddIntegrationProviderPayload.ts
deleted file mode 100644
index 04ea60b7f17..00000000000
--- a/packages/server/graphql/types/AddIntegrationProviderPayload.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {GraphQLNonNull, GraphQLObjectType} from 'graphql'
-import {GQLContext} from '../graphql'
-import IntegrationProvider from './IntegrationProvider'
-import makeMutationPayload from './makeMutationPayload'
-
-export const AddIntegrationProviderSuccess = new GraphQLObjectType({
- name: 'AddIntegrationProviderSuccess',
- fields: () => ({
- provider: {
- type: new GraphQLNonNull(IntegrationProvider),
- description: 'The provider that was added',
- resolve: async ({providerId}, _args, {dataLoader}) => {
- return dataLoader.get('integrationProviders').load(providerId)
- }
- }
- })
-})
-
-const AddIntegrationProviderPayload = makeMutationPayload(
- 'AddIntegrationProviderPayload',
- AddIntegrationProviderSuccess
-)
-
-export default AddIntegrationProviderPayload
diff --git a/packages/server/graphql/types/IntegrationProvider.ts b/packages/server/graphql/types/IntegrationProvider.ts
deleted file mode 100644
index e0a0bd6d2c0..00000000000
--- a/packages/server/graphql/types/IntegrationProvider.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import {GraphQLBoolean, GraphQLID, GraphQLInterfaceType, GraphQLNonNull} from 'graphql'
-import IntegrationProviderId from 'parabol-client/shared/gqlIds/IntegrationProviderId'
-import {TIntegrationProvider} from '../../postgres/queries/getIntegrationProvidersByIds'
-import GraphQLISO8601Type from './GraphQLISO8601Type'
-import IntegrationProviderAuthStrategyEnum from './IntegrationProviderAuthStrategyEnum'
-import IntegrationProviderScopeEnum from './IntegrationProviderScopeEnum'
-import IntegrationProviderServiceEnum from './IntegrationProviderServiceEnum'
-
-export const integrationProviderFields = () => ({
- id: {
- type: new GraphQLNonNull(GraphQLID),
- description: "The provider's unique identifier",
- resolve: ({id}: TIntegrationProvider) => IntegrationProviderId.join(id)
- },
- teamId: {
- type: GraphQLID,
- description: 'The team that belongs to the provider if team scoped'
- },
- orgId: {
- type: GraphQLID,
- description: 'The organization that belongs to the provider if org scoped'
- },
- createdAt: {
- type: new GraphQLNonNull(GraphQLISO8601Type),
- description: 'The timestamp the provider was created'
- },
- updatedAt: {
- type: new GraphQLNonNull(GraphQLISO8601Type),
- description: 'The timestamp the token was updated at'
- },
- service: {
- description: 'The name of the integration service (GitLab, Mattermost, etc)',
- type: new GraphQLNonNull(IntegrationProviderServiceEnum)
- },
- authStrategy: {
- description: 'The kind of token used by this provider (OAuth2, PAT, Webhook)',
- type: new GraphQLNonNull(IntegrationProviderAuthStrategyEnum)
- },
- scope: {
- description:
- 'The scope this provider configuration was created at (globally, org-wide, or by the team)',
- type: new GraphQLNonNull(IntegrationProviderScopeEnum)
- },
- isActive: {
- type: new GraphQLNonNull(GraphQLBoolean),
- description: 'true if the provider configuration should be used'
- }
-})
-
-const IntegrationProvider = new GraphQLInterfaceType({
- name: 'IntegrationProvider',
- description: 'An authentication provider configuration',
- fields: integrationProviderFields
-})
-
-export default IntegrationProvider
diff --git a/packages/server/graphql/types/IntegrationProviderOAuth1.ts b/packages/server/graphql/types/IntegrationProviderOAuth1.ts
deleted file mode 100644
index ac9631eeef8..00000000000
--- a/packages/server/graphql/types/IntegrationProviderOAuth1.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import {GraphQLNonNull, GraphQLObjectType} from 'graphql'
-import {GQLContext} from '../graphql'
-import GraphQLURLType from './GraphQLURLType'
-import IntegrationProvider, {integrationProviderFields} from './IntegrationProvider'
-
-const IntegrationProviderOAuth1 = new GraphQLObjectType({
- name: 'IntegrationProviderOAuth1',
- description: 'An integration provider that connects via OAuth1.0',
- interfaces: () => [IntegrationProvider],
- isTypeOf: ({authStrategy}) => authStrategy === 'oauth1',
- fields: () => ({
- ...integrationProviderFields(),
- serverBaseUrl: {
- type: new GraphQLNonNull(GraphQLURLType),
- description: 'The base URL of the OAuth1 server'
- }
- })
-})
-
-export default IntegrationProviderOAuth1
diff --git a/packages/server/graphql/types/IntegrationProviderOAuth2.ts b/packages/server/graphql/types/IntegrationProviderOAuth2.ts
deleted file mode 100644
index 3dbb591c5b1..00000000000
--- a/packages/server/graphql/types/IntegrationProviderOAuth2.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql'
-import {GQLContext} from '../graphql'
-import GraphQLURLType from './GraphQLURLType'
-import IntegrationProvider, {integrationProviderFields} from './IntegrationProvider'
-
-const IntegrationProviderOAuth2 = new GraphQLObjectType({
- name: 'IntegrationProviderOAuth2',
- description: 'An integration provider that connects via OAuth2',
- interfaces: () => [IntegrationProvider],
- isTypeOf: ({authStrategy}) => authStrategy === 'oauth2',
- fields: () => ({
- ...integrationProviderFields(),
- serverBaseUrl: {
- type: new GraphQLNonNull(GraphQLURLType),
- description: 'The base URL of the OAuth2 server'
- },
- clientId: {
- type: new GraphQLNonNull(GraphQLID),
- description: 'The OAuth2 client id'
- },
- tenantId: {
- type: GraphQLID,
- description: 'The tenant ID for Azure Active Directory Auth'
- }
- })
-})
-
-export default IntegrationProviderOAuth2
diff --git a/packages/server/graphql/types/IntegrationProviderSharedSecret.ts b/packages/server/graphql/types/IntegrationProviderSharedSecret.ts
deleted file mode 100644
index 05b16946dac..00000000000
--- a/packages/server/graphql/types/IntegrationProviderSharedSecret.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql'
-import {GQLContext} from '../graphql'
-import GraphQLURLType from './GraphQLURLType'
-import IntegrationProvider, {integrationProviderFields} from './IntegrationProvider'
-
-const IntegrationProviderSharedSecret = new GraphQLObjectType({
- name: 'IntegrationProviderSharedSecret',
- description: 'An integration provider that connects via a shared secret',
- interfaces: () => [IntegrationProvider],
- isTypeOf: ({authStrategy}) => authStrategy === 'sharedSecret',
- fields: () => ({
- ...integrationProviderFields(),
- serverBaseUrl: {
- type: new GraphQLNonNull(GraphQLURLType),
- description: 'The base URL of the OAuth1 server'
- },
- sharedSecret: {
- type: new GraphQLNonNull(GraphQLString),
- description: 'The shared secret used to sign requests'
- }
- })
-})
-
-export default IntegrationProviderSharedSecret
diff --git a/packages/server/graphql/types/IntegrationProviderWebhook.ts b/packages/server/graphql/types/IntegrationProviderWebhook.ts
deleted file mode 100644
index ee5aa8be1f2..00000000000
--- a/packages/server/graphql/types/IntegrationProviderWebhook.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import {GraphQLNonNull, GraphQLObjectType} from 'graphql'
-import {GQLContext} from '../graphql'
-import GraphQLURLType from './GraphQLURLType'
-import IntegrationProvider, {integrationProviderFields} from './IntegrationProvider'
-
-const IntegrationProviderWebhook = new GraphQLObjectType({
- name: 'IntegrationProviderWebhook',
- description: 'An integration provider that connects via webhook',
- interfaces: () => [IntegrationProvider],
- isTypeOf: ({authStrategy}) => authStrategy === 'webhook',
- fields: () => ({
- ...integrationProviderFields(),
- webhookUrl: {
- type: new GraphQLNonNull(GraphQLURLType),
- description: 'The webhook URL'
- }
- })
-})
-
-export default IntegrationProviderWebhook
diff --git a/packages/server/postgres/migrations/2025-02-28T15:28:11.536Z_notificationSettingsPerTeamAndChannel.ts b/packages/server/postgres/migrations/2025-02-28T15:28:11.536Z_notificationSettingsPerTeamAndChannel.ts
new file mode 100644
index 00000000000..c722a9c3574
--- /dev/null
+++ b/packages/server/postgres/migrations/2025-02-28T15:28:11.536Z_notificationSettingsPerTeamAndChannel.ts
@@ -0,0 +1,83 @@
+import {sql, type Kysely} from 'kysely'
+
+export async function up(db: Kysely): Promise {
+ // Schema changes significantly, easier to create a new table
+ await db.schema
+ .createTable('TeamNotificationSettings')
+ .addColumn('id', 'serial', (col) => col.primaryKey())
+ .addColumn('providerId', 'integer', (col) =>
+ col.references('IntegrationProvider.id').onDelete('cascade').notNull()
+ )
+ .addColumn('teamId', 'varchar(100)', (col) =>
+ col.references('Team.id').onDelete('cascade').notNull()
+ )
+ .addColumn('channelId', 'varchar(255)')
+ .addColumn('events', sql`"SlackNotificationEventEnum"[]`, (col) =>
+ col.defaultTo(sql`enum_range(NULL::"SlackNotificationEventEnum")`).notNull()
+ )
+ .addUniqueConstraint(
+ 'TeamNotificationSettings_providerId_teamId_channelId_key',
+ ['providerId', 'teamId', 'channelId'],
+ (uc) => uc.nullsNotDistinct()
+ )
+ .execute()
+
+ await db
+ .insertInto('TeamNotificationSettings')
+ .columns(['providerId', 'teamId', 'events'])
+ .expression((eb) =>
+ eb
+ .selectFrom('TeamMemberIntegrationAuth as auth')
+ .leftJoin('NotificationSettings as settings', 'auth.id', 'settings.authId')
+ .select(['auth.providerId', 'auth.teamId', sql`array_remove(array_agg(event), NULL)`])
+ // There was a bug which might have added settings for other providers like gcal
+ .where((eb) => eb.or([eb('service', '=', 'mattermost'), eb('service', '=', 'msTeams')]))
+ .groupBy(['auth.providerId', 'auth.teamId'])
+ )
+ .onConflict((oc) =>
+ oc.constraint('TeamNotificationSettings_providerId_teamId_channelId_key').doNothing()
+ )
+ .execute()
+
+ /* dropping the old table will be done in a later change
+ await db.schema
+ .dropTable('NotificationSettings')
+ .execute()
+ */
+}
+
+export async function down(db: Kysely): Promise {
+ /*
+ await db.schema
+ .createTable('NotificationSettings')
+ .addColumn('id', 'serial', (col) => col.primaryKey())
+ .addColumn('authId', 'integer', (col) =>
+ col.references('TeamMemberIntegrationAuth.id').onDelete('cascade').notNull()
+ )
+ .addColumn('event', sql`"SlackNotificationEventEnum"`, (col) => col.notNull())
+ .addUniqueConstraint('NotificationSettings_authId_event_key', ['authId', 'event'])
+ .execute()
+
+ await db.schema
+ .createIndex('NotificationSettings_authId_idx')
+ .on('NotificationSettings')
+ .column('authId')
+ .execute()
+
+ await db
+ .insertInto('NotificationSettings')
+ .columns(['authId', 'event'])
+ .expression((eb) =>
+ eb
+ .selectFrom('TeamMemberIntegrationAuth as auth')
+ .innerJoin('TeamNotificationSettings as settings', (join) => join
+ .onRef('auth.teamId', '=', 'settings.teamId')
+ .onRef('auth.providerId', '=', 'settings.providerId')
+ )
+ .select(['auth.id', sql`unnest(events)`])
+ )
+ .execute()
+ */
+
+ await db.schema.dropTable('TeamNotificationSettings').execute()
+}
diff --git a/packages/server/postgres/queries/upsertIntegrationProvider.ts b/packages/server/postgres/queries/upsertIntegrationProvider.ts
index a3221ff4766..890cd13f7f8 100644
--- a/packages/server/postgres/queries/upsertIntegrationProvider.ts
+++ b/packages/server/postgres/queries/upsertIntegrationProvider.ts
@@ -11,7 +11,7 @@ interface IUpsertIntegrationProviderInput {
authStrategy: IntegrationProviderAuthStrategyEnum
scope?: IntegrationProviderScopeEnum | null | undefined
clientId?: string
- tenantId?: string
+ tenantId?: string | null
clientSecret?: string
serverBaseUrl?: string
webhookUrl?: string
diff --git a/yarn.lock b/yarn.lock
index e564cb9b901..03b89b091bf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -16705,17 +16705,18 @@ koalas@^1.0.2:
resolved "https://registry.yarnpkg.com/koalas/-/koalas-1.0.2.tgz#318433f074235db78fae5661a02a8ca53ee295cd"
integrity sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA==
-kysely-codegen@^0.15.0:
- version "0.15.0"
- resolved "https://registry.yarnpkg.com/kysely-codegen/-/kysely-codegen-0.15.0.tgz#771c0256c24897ea64d5713dc10e40e8a359b96b"
- integrity sha512-LPta2nQOyoEPDQ3w/Gsplc+2iyZPAsGvtWoS21VzOB0NDQ0B38Xy1gS8WlbGef542Zdw2eLJHxekud9DzVdNRw==
+kysely-codegen@^0.17.0:
+ version "0.17.0"
+ resolved "https://registry.yarnpkg.com/kysely-codegen/-/kysely-codegen-0.17.0.tgz#07bb2182ce2f315953c2407a52c99ee1ee942f91"
+ integrity sha512-C36g6epial8cIOSBEWGI9sRfkKSsEzTcivhjPivtYFQnhMdXnrVFaUe7UMZHeSdXaHiWDqDOkReJgWLD8nPKdg==
dependencies:
chalk "4.1.2"
dotenv "^16.4.5"
dotenv-expand "^11.0.6"
git-diff "^2.0.6"
- micromatch "^4.0.5"
+ micromatch "^4.0.8"
minimist "^1.2.8"
+ pluralize "^8.0.0"
kysely-ctl@^0.11.0:
version "0.11.0"
@@ -19680,6 +19681,11 @@ please-upgrade-node@^3.2.0:
dependencies:
semver-compare "^1.0.0"
+pluralize@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
+ integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
+
pm2-axon-rpc@~0.7.0, pm2-axon-rpc@~0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz#2daec5383a63135b3f18babb70266dacdcbc429a"
@@ -22521,7 +22527,7 @@ string-similarity@^3.0.0:
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7"
integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ==
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -22539,6 +22545,15 @@ string-width@^1.0.1:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -22625,7 +22640,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -22639,6 +22654,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
dependencies:
ansi-regex "^2.0.0"
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -24673,7 +24695,7 @@ workbox-window@6.6.1:
"@types/trusted-types" "^2.0.2"
workbox-core "6.6.1"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -24691,6 +24713,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"