diff --git a/src/server/database/migrations/20170821152300-providerTeamIdsToTeamIdjs.js b/src/server/database/migrations/20170821152300-providerTeamIdsToTeamIdjs.js new file mode 100644 index 00000000000..a082c7a47d5 --- /dev/null +++ b/src/server/database/migrations/20170821152300-providerTeamIdsToTeamIdjs.js @@ -0,0 +1,56 @@ +exports.up = async (r) => { + const tables = [ + r.table('Provider').replace((row) => { + return row + .merge({ + teamId: row('teamIds')(0).default(null), + isActive: row('teamIds').count().ne(0) + }) + .without('teamIds'); + }) + ]; + try { + await Promise.all(tables); + } catch (e) { + console.log('Exception during Promise.all(tables)'); + } + const indices = [ + r.table('Provider').indexDrop('teamIds'), + r.table('Provider').indexCreate('teamId') + ]; + try { + await Promise.all(indices); + } catch (e) { + console.log('Exception during Promise.all(indices)'); + } +}; + +exports.down = async (r) => { + const tables = [ + r.tableDrop('Provider').replace((row) => { + return row + .merge({ + teamIds: r.branch( + row('teamId'), + [row('teamId')], + [] + ) + }) + .without('teamId', 'isActive'); + }) + ]; + try { + await Promise.all(tables); + } catch (e) { + console.log('Exception during Promise.all(tables)'); + } + const indices = [ + r.table('Provider').indexCreate('teamIds', {multi: true}), + r.table('Provider').indexDrop('teamId') + ]; + try { + await Promise.all(indices); + } catch (e) { + console.log('Exception during Promise.all(indices)'); + } +}; diff --git a/src/server/graphql/models/Team/notifySlack/notifySlack.js b/src/server/graphql/models/Team/notifySlack/notifySlack.js index 89abfac4abd..2f282dc23f8 100644 --- a/src/server/graphql/models/Team/notifySlack/notifySlack.js +++ b/src/server/graphql/models/Team/notifySlack/notifySlack.js @@ -14,8 +14,8 @@ const getIntegrationsForNotification = (teamId, notification) => { const notifySlack = async (integrations, teamId, slackText) => { const r = getRethink(); const provider = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) - .filter({service: SLACK}) + .getAll(teamId, {index: 'teamId'}) + .filter({service: SLACK, isActive: true}) .nth(0); // for each slack channel, send a notification for (let i = 0; i < integrations.length; i++) { diff --git a/src/server/graphql/models/TeamMember/removeTeamMember/removeAllTeamMembers.js b/src/server/graphql/models/TeamMember/removeTeamMember/removeAllTeamMembers.js index 8ac65eedaf2..340c03dd572 100644 --- a/src/server/graphql/models/TeamMember/removeTeamMember/removeAllTeamMembers.js +++ b/src/server/graphql/models/TeamMember/removeTeamMember/removeAllTeamMembers.js @@ -97,15 +97,15 @@ export default async function removeAllTeamMembers(maybeTeamMemberIds, exchange) // TODO on the frontend, pop a warning if this is the last guy const changedProviders = await r.table('Provider') - .getAll(r.args(teamIds), {index: 'teamIds'}) - .filter({userId}) - .update((doc) => ({ - teamIds: doc('teamIds').filter((teamId) => r.expr(teamIds).contains(teamId).not()) - }), {returnChanges: true})('changes').default([]); + .getAll(r.args(teamIds), {index: 'teamId'}) + .filter({userId, isActive: true}) + .update({ + isActive: false + }, {returnChanges: true})('changes').default([]); const changedGitHubIntegrations = changedProviders.some((change) => change.new_val.service === GITHUB); if (changedGitHubIntegrations) { - const repoChanges = removeGitHubReposForUserId(userId, [teamId]); + const repoChanges = removeGitHubReposForUserId(userId, teamIds); // TODO send the archived projects in a mutation payload await archiveProjectsForManyRepos(repoChanges); } diff --git a/src/server/graphql/mutations/addGitHubRepo.js b/src/server/graphql/mutations/addGitHubRepo.js index 4cc0fba601f..f562bb4b53c 100644 --- a/src/server/graphql/mutations/addGitHubRepo.js +++ b/src/server/graphql/mutations/addGitHubRepo.js @@ -36,7 +36,6 @@ query getOrg($login: String!) { databaseId } }`; - const createOrgWebhook = async (accessToken, nameWithOwner) => { const [owner] = nameWithOwner.split('/'); const endpoint = `https://api.github.com/orgs/${owner}/hooks`; @@ -81,8 +80,8 @@ export default { // VALIDATION const allTeamProviders = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) - .filter({service: GITHUB}); + .getAll(teamId, {index: 'teamId'}) + .filter({service: GITHUB, isActive: true}); const viewerProviderIdx = allTeamProviders.findIndex((provider) => provider.userId === userId); if (viewerProviderIdx === -1) { diff --git a/src/server/graphql/mutations/addSlackChannel.js b/src/server/graphql/mutations/addSlackChannel.js index ab535cf7f03..d4f4eb12b0c 100644 --- a/src/server/graphql/mutations/addSlackChannel.js +++ b/src/server/graphql/mutations/addSlackChannel.js @@ -42,8 +42,8 @@ export default { // get the user's token const provider = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) - .filter({service: SLACK}) + .getAll(teamId, {index: 'teamId'}) + .filter({service: SLACK, isActive: true}) .nth(0) .default(null); diff --git a/src/server/graphql/mutations/createGitHubIssue.js b/src/server/graphql/mutations/createGitHubIssue.js index 9bd779214d7..a4a4de12229 100644 --- a/src/server/graphql/mutations/createGitHubIssue.js +++ b/src/server/graphql/mutations/createGitHubIssue.js @@ -85,8 +85,8 @@ export default { const {teamMemberId: assigneeTeamMemberId, content: rawContentStr} = project; const [assigneeUserId] = assigneeTeamMemberId.split('::'); const providers = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) - .filter({service: GITHUB}); + .getAll(teamId, {index: 'teamId'}) + .filter({service: GITHUB, isActive: true}); const assigneeProvider = providers.find((provider) => provider.userId === assigneeUserId); if (!assigneeProvider) { const assigneeName = await r.table('TeamMember').get(assigneeTeamMemberId)('preferredName'); diff --git a/src/server/graphql/mutations/githubAddAssignee.js b/src/server/graphql/mutations/githubAddAssignee.js index d14b24013ce..bcaa6e3f6b0 100644 --- a/src/server/graphql/mutations/githubAddAssignee.js +++ b/src/server/graphql/mutations/githubAddAssignee.js @@ -42,8 +42,7 @@ export default { // plural for each organization const providers = await r.table('Provider') .getAll(assigneeLogin, {index: 'providerUserId'}) - .filter({service: GITHUB}) - .filter((doc) => doc('teamIds').count().ne(0)); + .filter({service: GITHUB, isActive: true}) // .nth(0)('userId') // .default(null); diff --git a/src/server/graphql/mutations/githubAddMember.js b/src/server/graphql/mutations/githubAddMember.js index 9f5f56fa381..975ea9faafc 100644 --- a/src/server/graphql/mutations/githubAddMember.js +++ b/src/server/graphql/mutations/githubAddMember.js @@ -30,12 +30,11 @@ export default { // look the person up by their github user name on the provider table const providers = await r.table('Provider') .getAll(userName, {index: 'providerUserId'}) - .filter({service: GITHUB}) - .filter((doc) => doc('teamIds').count().ne(0)) - .pluck('accessToken', 'userId', 'teamIds') + .filter({service: GITHUB, isActive: true}) + .pluck('accessToken', 'userId', 'teamId') .merge((provider) => ({ repos: r.table(GITHUB) - .getAll(r.args(provider('teamIds')), {index: 'teamId'}) + .getAll(provider('teamId'), {index: 'teamId'}) .filter({isActive: true}) .filter((doc) => doc('nameWithOwner').match(`^${orgName}`)) .pluck('id', 'nameWithOwner', 'teamId') diff --git a/src/server/graphql/mutations/githubRemoveMember.js b/src/server/graphql/mutations/githubRemoveMember.js index 0519b2f94ee..6d7e42a7936 100644 --- a/src/server/graphql/mutations/githubRemoveMember.js +++ b/src/server/graphql/mutations/githubRemoveMember.js @@ -29,8 +29,7 @@ export default { const userId = await r.table('Provider') .getAll(userName, {index: 'providerUserId'}) - .filter({service: GITHUB}) - .filter((doc) => doc('teamIds').count().ne(0)) + .filter({service: GITHUB, isActive: true}) .nth(0)('userId') .default(null); diff --git a/src/server/graphql/mutations/joinIntegration.js b/src/server/graphql/mutations/joinIntegration.js index 7233c1507b5..9e5bf799a41 100644 --- a/src/server/graphql/mutations/joinIntegration.js +++ b/src/server/graphql/mutations/joinIntegration.js @@ -40,8 +40,8 @@ export default { } const provider = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) - .filter({service, userId}) + .getAll(teamId, {index: 'teamId'}) + .filter({service, userId, isActive: true}) .nth(0) .default(null); if (!provider) { diff --git a/src/server/graphql/mutations/removeProvider.js b/src/server/graphql/mutations/removeProvider.js index 86a551a60cf..a40fe76625f 100644 --- a/src/server/graphql/mutations/removeProvider.js +++ b/src/server/graphql/mutations/removeProvider.js @@ -54,7 +54,9 @@ export default { // unlink the team from the user's token const res = await r.table('Provider') .get(dbProviderId) - .update((user) => ({teamIds: user('teamIds').difference([teamId])}), {returnChanges: true}); + .update({ + isActive: false + }, {returnChanges: true}); if (res.skipped === 1) { throw new Error(`Provider ${providerId} does not exist`); @@ -78,7 +80,7 @@ export default { getPubSub().publish(`providerRemoved.${teamId}`, {providerRemoved, mutatorId: socket.id}); return providerRemoved; } else if (service === GITHUB) { - const repoChanges = removeGitHubReposForUserId(userId, [teamId]); + const repoChanges = await removeGitHubReposForUserId(userId, [teamId]); const providerRemoved = await getPayload(service, repoChanges, teamId, userId); const archivedProjectsByRepo = await archiveProjectsForManyRepos(repoChanges); providerRemoved.archivedProjectIds = archivedProjectsByRepo.reduce((arr, repoArr) => { diff --git a/src/server/graphql/queries/integrationProvider.js b/src/server/graphql/queries/integrationProvider.js index 81666b8ff58..c4ba7f822cd 100644 --- a/src/server/graphql/queries/integrationProvider.js +++ b/src/server/graphql/queries/integrationProvider.js @@ -27,8 +27,8 @@ export default { // RESOLUTION return r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) - .filter({service}) + .getAll(teamId, {index: 'teamId'}) + .filter({service, isActive: true}) .filter((doc) => doc('service').eq(SLACK).or(doc('userId').eq(userId))) .nth(0) .default(null); diff --git a/src/server/graphql/queries/providerMap.js b/src/server/graphql/queries/providerMap.js index b65c1ec85de..634b05b281c 100644 --- a/src/server/graphql/queries/providerMap.js +++ b/src/server/graphql/queries/providerMap.js @@ -31,7 +31,8 @@ export default { // RESOLUTION const allProviders = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) + .getAll(teamId, {index: 'teamId'}) + .filter({isActive: true}) .group('service') .ungroup() .merge((row) => ({ diff --git a/src/server/graphql/types/Provider.js b/src/server/graphql/types/Provider.js index 8e8511fbecb..e1f4d39e152 100644 --- a/src/server/graphql/types/Provider.js +++ b/src/server/graphql/types/Provider.js @@ -1,7 +1,7 @@ -import {GraphQLID, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql'; -import GraphQLISO8601Type from 'server/graphql/types/GraphQLISO8601Type'; +import {GraphQLBoolean, GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql'; import {connectionDefinitions, globalIdField} from 'graphql-relay'; import {nodeInterface} from 'server/graphql/models/Node/nodeQuery'; +import GraphQLISO8601Type from 'server/graphql/types/GraphQLISO8601Type'; import IntegrationService from 'server/graphql/types/IntegrationService'; const Provider = new GraphQLObjectType({ @@ -19,6 +19,10 @@ const Provider = new GraphQLObjectType({ type: GraphQLISO8601Type, description: 'The timestamp the provider was created' }, + isActive: { + type: GraphQLBoolean, + description: 'True if the Provider is active. else false' + }, providerUserId: { type: GraphQLID, description: '*The id for the user used by the provider, eg SlackTeamId, GoogleUserId, githubLogin' @@ -31,9 +35,9 @@ const Provider = new GraphQLObjectType({ type: IntegrationService, description: 'The name of the service' }, - teamIds: { - type: new GraphQLList(GraphQLID), - description: '*The teams that the token is linked to, if any' + teamId: { + type: GraphQLID, + description: '*The team that the token is linked to' }, updatedAt: { type: GraphQLISO8601Type, diff --git a/src/server/safeMutations/addProviderGitHub.js b/src/server/safeMutations/addProviderGitHub.js index 31fd19c9439..246e9fbbf0a 100644 --- a/src/server/safeMutations/addProviderGitHub.js +++ b/src/server/safeMutations/addProviderGitHub.js @@ -71,7 +71,7 @@ const addProviderGitHub = async (code, teamId, userId) => { } const {data: {viewer: {login: providerUserName}}} = gqlRes; const providerChange = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) + .getAll(teamId, {index: 'teamId'}) .filter({service: GITHUB, userId}) .nth(0)('id') .default(null) @@ -83,11 +83,12 @@ const addProviderGitHub = async (code, teamId, userId) => { id: shortid.generate(), accessToken, createdAt: now, + isActive: true, // github userId is never used for queries, but the login is! providerUserId: providerUserName, providerUserName, service: GITHUB, - teamIds: [teamId], + teamId, updatedAt: now, userId }, {returnChanges: true})('changes')(0), @@ -95,6 +96,7 @@ const addProviderGitHub = async (code, teamId, userId) => { .get(providerId) .update({ accessToken, + isActive: true, updatedAt: now, providerUserId: providerUserName, providerUserName @@ -104,7 +106,7 @@ const addProviderGitHub = async (code, teamId, userId) => { const provider = providerChange.new_val; const rowDetails = await getProviderRowData(GITHUB, teamId); - const isUpdate = Boolean(providerChange.old_val); + const isUpdate = providerChange.old_val.isActive; const joinedIntegrationIds = await getJoinedIntegrationIds(teamId, provider, rowDetails.integrationCount, isUpdate); const teamMemberId = `${userId}::${teamId}`; const teamMember = await getTeamMember(joinedIntegrationIds, teamMemberId); diff --git a/src/server/safeMutations/addProviderSlack.js b/src/server/safeMutations/addProviderSlack.js index 39f37584ffb..fb01f2a4589 100644 --- a/src/server/safeMutations/addProviderSlack.js +++ b/src/server/safeMutations/addProviderSlack.js @@ -36,7 +36,7 @@ const addProviderSlack = async (code, teamId, userId) => { throw new Error(`bad scope: ${scope}`); } const provider = await r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) + .getAll(teamId, {index: 'teamId'}) .filter({service: SLACK}) .nth(0)('id') .default(null) @@ -48,10 +48,11 @@ const addProviderSlack = async (code, teamId, userId) => { id: shortid.generate(), accessToken, createdAt: now, + isActive: true, providerUserId, providerUserName, service: SLACK, - teamIds: [teamId], + teamId, updatedAt: now, userId }, {returnChanges: true})('changes')(0)('new_val'), @@ -59,6 +60,7 @@ const addProviderSlack = async (code, teamId, userId) => { .get(providerId) .update({ accessToken, + isActive: true, updatedAt: now }, {returnChanges: true})('changes')(0)('new_val') ); diff --git a/src/server/safeMutations/archiveProjectsForManyRepos.js b/src/server/safeMutations/archiveProjectsForManyRepos.js index 934cfb9fefc..49df5643bb9 100644 --- a/src/server/safeMutations/archiveProjectsForManyRepos.js +++ b/src/server/safeMutations/archiveProjectsForManyRepos.js @@ -1,7 +1,8 @@ import archiveProjectsByGitHubRepo from 'server/safeMutations/archiveProjectsByGitHubRepo'; const archiveProjectsForManyRepos = async (integrationChanges) => { - return Promise.all(integrationChanges.map(({new_val: {isActive, teamId, nameWithOwner}}) => { + return Promise.all(integrationChanges.map((change) => { + const {new_val: {isActive, teamId, nameWithOwner}} = change; if (!isActive) { return archiveProjectsByGitHubRepo(teamId, nameWithOwner); } diff --git a/src/server/safeQueries/getProviderRowData.js b/src/server/safeQueries/getProviderRowData.js index 39ab4ff46cc..89c25c7da97 100644 --- a/src/server/safeQueries/getProviderRowData.js +++ b/src/server/safeQueries/getProviderRowData.js @@ -8,8 +8,8 @@ const getProviderRowData = (service, teamId) => { .filter({isActive: true}) .count(), userCount: r.table('Provider') - .getAll(teamId, {index: 'teamIds'}) - .filter({service}) + .getAll(teamId, {index: 'teamId'}) + .filter({service, isActive: true}) .count() }); };