From 066eea9c30b66e76fc1d45cdd70b29d8f399d688 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:30:24 -0700 Subject: [PATCH] chore(release): Test v7.38.1 (#9934) Signed-off-by: Matt Krick Signed-off-by: dependabot[bot] Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> Co-authored-by: Georg Bremer Co-authored-by: Jordan Husney Co-authored-by: Matt Krick Co-authored-by: Rafael Romero Co-authored-by: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Co-authored-by: Bruce Tian Co-authored-by: Nick O'Ferrall Co-authored-by: snyk-bot Co-authored-by: Marcus Wermuth Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: GitHub Action Co-authored-by: Dale Bumblis <135627447+dbumblis-parabol@users.noreply.github.com> Co-authored-by: Mohd Muneeb Co-authored-by: Muneeb-Ventures Co-authored-by: Bartosz Jarocki Co-authored-by: Terry Acker Co-authored-by: github-actions --- .env.example | 3 + .gitignore | 1 + .release-please-manifest.json | 2 +- CHANGELOG.md | 17 ++++++ package.json | 2 +- packages/chronos/package.json | 4 +- .../ActivityLibrary/ScheduleMeetingButton.tsx | 7 +-- .../client/components/AddToGitHubMenuItem.tsx | 1 + .../client/components/AddToJiraMenuItem.tsx | 1 + packages/client/components/ScheduleDialog.tsx | 5 +- packages/client/components/ScopePhaseArea.tsx | 16 +++++- .../StageTimerModalEndTimeSlackToggle.tsx | 2 +- .../TeamPrompt/TeamPromptWorkDrawer.tsx | 39 ++++++++++--- .../ProviderRow/AtlassianProviderRow.tsx | 2 + .../ProviderRow/GitHubProviderRow.tsx | 2 + .../ProviderRow/MSTeamsProviderRow.tsx | 3 + .../ProviderRow/MattermostProviderRow.tsx | 2 + .../ProviderRow/SlackProviderRow.tsx | 2 + .../components/OrgBilling/BillingForm.tsx | 33 ++++------- packages/client/package.json | 2 +- packages/client/types/modules.d.ts | 2 + .../client/utils/AtlassianClientManager.ts | 2 + packages/client/utils/GitHubClientManager.ts | 1 + packages/client/utils/SlackClientManager.ts | 1 + .../indexing/orgIdsWithFeatureFlag.ts | 15 ----- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +- packages/integration-tests/package.json | 2 +- .../server/billing/helpers/adjustUserCount.ts | 49 ++++++++--------- .../server/billing/helpers/generateInvoice.ts | 13 ++--- .../helpers/generateUpcomingInvoice.ts | 5 +- .../server/billing/helpers/teamLimitsCheck.ts | 36 ++++++++---- .../billing/helpers/terminateSubscription.ts | 48 ++++++++++------ .../server/billing/stripeWebhookHandler.ts | 13 +---- .../database/types/GoogleAnalyzedEntity.ts | 7 --- .../database/types/processTeamsLimitsJob.ts | 10 +++- packages/server/graphql/mutations/addOrg.ts | 6 +- packages/server/graphql/mutations/addTeam.ts | 2 +- .../graphql/mutations/createReflection.ts | 4 +- .../graphql/mutations/downgradeToStarter.ts | 6 +- .../mutations/helpers/bootstrapNewUser.ts | 2 +- .../graphql/mutations/helpers/createNewOrg.ts | 5 ++ .../mutations/helpers/createTeamAndLeader.ts | 9 ++- .../mutations/helpers/hideConversionModal.ts | 6 ++ .../mutations/helpers/oldUpgradeToTeamTier.ts | 2 +- .../mutations/helpers/removeFromOrg.ts | 4 +- .../helpers/resolveDowngradeToStarter.ts | 16 +++++- .../helpers/safeCreateRetrospective.ts | 2 +- .../server/graphql/mutations/moveTeamToOrg.ts | 4 +- packages/server/graphql/mutations/payLater.ts | 8 +++ .../mutations/updateReflectionContent.ts | 4 +- .../private/mutations/changeEmailDomain.ts | 5 ++ .../mutations/draftEnterpriseInvoice.ts | 21 +++++++ .../graphql/private/mutations/endTrial.ts | 1 + .../private/mutations/flagConversionModal.ts | 12 +++- .../mutations/sendUpcomingInvoiceEmails.ts | 6 ++ .../mutations/setOrganizationDomain.ts | 13 ++++- .../graphql/private/mutations/startTrial.ts | 5 ++ ...ription.ts => stripeCreateSubscription.ts} | 23 +++++++- .../mutations/stripeDeleteSubscription.ts | 7 ++- .../mutations/stripeInvoiceFinalized.ts | 4 +- .../private/mutations/stripeInvoicePaid.ts | 9 ++- .../private/mutations/stripeSucceedPayment.ts | 9 ++- .../mutations/stripeUpdateCreditCard.ts | 10 ++++ .../private/mutations/updateOrgFeatureFlag.ts | 21 ++++++- .../private/mutations/upgradeToTeamTier.ts | 41 +++++++++----- .../graphql/private/typeDefs/_legacy.graphql | 4 +- .../mutations/acceptRequestToJoinDomain.ts | 9 ++- .../mutations/createStripeSubscription.ts | 4 +- .../public/mutations/updateCreditCard.ts | 14 +++++ .../graphql/public/mutations/updateOrg.ts | 6 ++ .../public/mutations/uploadOrgImage.ts | 7 ++- .../graphql/public/types/DomainJoinRequest.ts | 9 ++- packages/server/graphql/queries/invoices.ts | 2 +- packages/server/package.json | 4 +- .../server/postgres/helpers/toCreditCard.ts | 6 ++ .../helpers/toGoogleAnalyzedEntity.ts | 6 ++ .../1719435764047_Organization-phase1.ts | 55 +++++++++++++++++++ .../safeArchiveEmptyStarterOrganization.ts | 8 ++- packages/server/socketHandlers/handleOpen.ts | 15 +++-- .../isRequestToJoinDomainAllowed.test.ts | 7 ++- packages/server/utils/stripe/StripeManager.ts | 30 +++++----- .../toolboxSrc/applyEnvVarsToClientAssets.ts | 2 + scripts/toolboxSrc/primeIntegrations.ts | 4 +- scripts/toolboxSrc/setIsEnterprise.ts | 11 ++-- scripts/webpack/dev.client.config.js | 2 + 86 files changed, 581 insertions(+), 249 deletions(-) delete mode 100644 packages/embedder/indexing/orgIdsWithFeatureFlag.ts rename packages/server/graphql/private/mutations/{stripeUpdateSubscription.ts => stripeCreateSubscription.ts} (57%) create mode 100644 packages/server/postgres/helpers/toCreditCard.ts create mode 100644 packages/server/postgres/helpers/toGoogleAnalyzedEntity.ts create mode 100644 packages/server/postgres/migrations/1719435764047_Organization-phase1.ts diff --git a/.env.example b/.env.example index 71aed091304..a3835e093d3 100644 --- a/.env.example +++ b/.env.example @@ -112,6 +112,7 @@ RETHINKDB_SSL='false' # GITHUB_CLIENT_SECRET='key_GITHUB_CLIENT_SECRET' # GITLAB_CLIENT_ID='key_GITLAB_CLIENT_ID' # GITLAB_CLIENT_SECRET='key_GITLAB_CLIENT_SECRET' +# GITLAB_SERVER_URL='https://gitlab.com' # HUBSPOT_API_KEY='' # HUBSPOT_SALES_PIPELINE_ACTIVE_STAGES='' # HUBSPOT_SALES_PIPELINE_ID='' @@ -123,6 +124,8 @@ RETHINKDB_SSL='false' # STRIPE_SECRET_KEY='' # STRIPE_PUBLISHABLE_KEY='' # STRIPE_WEBHOOK_SECRET='' +# MATTERMOST_DISABLED='false' +# MSTEAMS_DISABLED='false' # MAIL # MAIL GLOBALS. PROVIDER: mailgun | google | debug | smtp diff --git a/.gitignore b/.gitignore index dc26e75b673..805d98ce9ef 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ storybook-static/ test-report.xml webpack-assets.json **/data +backups/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ba8fdca4366..aa061992a46 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.0" + ".": "7.38.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4868aa5c7..63e36d1a0d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.1](https://github.com/ParabolInc/parabol/compare/v7.38.0...v7.38.1) (2024-07-04) + + +### Fixed + +* Allow starting recurring meetings without GCal ([#9920](https://github.com/ParabolInc/parabol/issues/9920)) ([3f2ca48](https://github.com/ParabolInc/parabol/commit/3f2ca482aef98cf07a7f27b6a3872c9505735334)) +* connectionContext always available ([#9923](https://github.com/ParabolInc/parabol/issues/9923)) ([1dce636](https://github.com/ParabolInc/parabol/commit/1dce6366ae968718dfa72c44553201a016863213)) +* handle failed 3DS payments ([#9924](https://github.com/ParabolInc/parabol/issues/9924)) ([4663e9e](https://github.com/ParabolInc/parabol/commit/4663e9ea28f36dcf10bfe21347912865d22a8872)) + + +### Changed + +* **gitignore:** ignore anything on the backups folder ([068f91e](https://github.com/ParabolInc/parabol/commit/068f91e33e0d3c160c67f52f8008a177eb5c326d)) +* Read Gitlab server URL from env for prime integrations ([#9910](https://github.com/ParabolInc/parabol/issues/9910)) ([830235d](https://github.com/ParabolInc/parabol/commit/830235ddb5afe4d3e0731181c76930ec0307609d)) +* **rethinkdb:** Organization: Phase 1 ([#9883](https://github.com/ParabolInc/parabol/issues/9883)) ([6bb5fb2](https://github.com/ParabolInc/parabol/commit/6bb5fb2c2cfc0ba77679633acd2a21ac04fcbfd3)) +* Show only available integrations ([#9908](https://github.com/ParabolInc/parabol/issues/9908)) ([04bfa6c](https://github.com/ParabolInc/parabol/commit/04bfa6c69c07be8a190542db4a5fb907e43d67ad)) + ## [7.38.0](https://github.com/ParabolInc/parabol/compare/v7.37.8...v7.38.0) (2024-07-02) diff --git a/package.json b/package.json index 96a4276a340..24fd810e27b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.38.0", + "version": "7.38.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 2b1996dac6f..58c4e103b81 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.0", + "version": "7.38.1", "description": "A cron job scheduler", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/chronos#readme", @@ -25,6 +25,6 @@ }, "dependencies": { "cron": "^2.3.1", - "parabol-server": "7.38.0" + "parabol-server": "7.38.1" } } diff --git a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx index 8f2017c7d27..3b66168331c 100644 --- a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx +++ b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx @@ -39,15 +39,10 @@ const ScheduleMeetingButton = (props: Props) => { fragment ScheduleMeetingButton_team on Team { id viewerTeamMember { - isSelf integrations { gcal { - auth { - id - } cloudProvider { id - clientId } } } @@ -73,7 +68,7 @@ const ScheduleMeetingButton = (props: Props) => { closeModal() } - if (!cloudProvider) return null + if (!cloudProvider && !withRecurrence) return null return ( <> diff --git a/packages/client/components/AddToGitHubMenuItem.tsx b/packages/client/components/AddToGitHubMenuItem.tsx index e63cefbc27a..94e381b6f35 100644 --- a/packages/client/components/AddToGitHubMenuItem.tsx +++ b/packages/client/components/AddToGitHubMenuItem.tsx @@ -28,6 +28,7 @@ const AddToGitHubMenuItem = forwardRef((props: Props, ref) => { const openOAuth = () => { GitHubClientManager.openOAuth(atmosphere, teamId, mutationProps) } + if (!GitHubClientManager.isAvailable) return null return ( { const onClick = () => { AtlassianClientManager.openOAuth(atmosphere, teamId, mutationProps) } + if (!AtlassianClientManager.isAvailable) return null return ( { } } + const subTitle = `Create a ${withRecurrence ? 'recurring meeting series' : 'meeting'}${gcal?.cloudProvider ? ' or add the meeting to your calendar.' : '.'}` return (
Schedule Your Meeting
-
- Create a recurring meeting series or add the meeting to your calendar. -
+
{subTitle}
{ const allowJiraServer = !!jiraServerIntegration?.sharedProviders.length const baseTabs = [ - {icon: , label: 'GitHub', allow: true, Component: ScopePhaseAreaGitHub}, - {icon: , label: 'Jira', allow: true, Component: ScopePhaseAreaJira}, + { + icon: , + label: 'GitHub', + allow: GitHubClientManager.isAvailable, + Component: ScopePhaseAreaGitHub + }, + { + icon: , + label: 'Jira', + allow: AtlassianClientManager.isAvailable, + Component: ScopePhaseAreaJira + }, { icon: , label: 'Parabol', diff --git a/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx b/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx index 39b695a17fd..fc3cc490e30 100644 --- a/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx +++ b/packages/client/components/StageTimerModalEndTimeSlackToggle.tsx @@ -119,7 +119,7 @@ const StageTimerModalEndTimeSlackToggle = (props: Props) => { } return ( - {(slack?.isActive || noActiveIntegrations) && ( + {SlackClientManager.isAvailable && (slack?.isActive || noActiveIntegrations) && ( diff --git a/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx b/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx index 836d3aea0e2..bdf7baf6d3a 100644 --- a/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx +++ b/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx @@ -5,6 +5,8 @@ import {useFragment} from 'react-relay' import {TeamPromptWorkDrawer_meeting$key} from '../../__generated__/TeamPromptWorkDrawer_meeting.graphql' import useAtmosphere from '../../hooks/useAtmosphere' import gcalLogo from '../../styles/theme/images/graphics/google-calendar.svg' +import AtlassianClientManager from '../../utils/AtlassianClientManager' +import GitHubClientManager from '../../utils/GitHubClientManager' import SendClientSideEvent from '../../utils/SendClientSideEvent' import GitHubSVG from '../GitHubSVG' import JiraSVG from '../JiraSVG' @@ -44,6 +46,11 @@ const TeamPromptWorkDrawer = (props: Props) => { id } } + gcal { + cloudProvider { + id + } + } } } } @@ -54,6 +61,7 @@ const TeamPromptWorkDrawer = (props: Props) => { const atmosphere = useAtmosphere() const hasJiraServer = !!meeting.viewerMeetingMember?.teamMember?.integrations.jiraServer?.sharedProviders?.length + const hasGCal = !!meeting.viewerMeetingMember?.teamMember?.integrations.gcal?.cloudProvider?.id useEffect(() => { SendClientSideEvent(atmosphere, 'Your Work Drawer Impression', { @@ -81,14 +89,29 @@ const TeamPromptWorkDrawer = (props: Props) => { } ] : []), - {icon: , service: 'github', label: 'GitHub', Component: GitHubIntegrationPanel}, - {icon: , service: 'jira', label: 'Jira', Component: JiraIntegrationPanel}, - { - icon: , - service: 'gcal', - label: 'Google Calendar', - Component: GCalIntegrationPanel - } + ...(GitHubClientManager.isAvailable + ? [ + { + icon: , + service: 'github', + label: 'GitHub', + Component: GitHubIntegrationPanel + } + ] + : []), + ...(AtlassianClientManager.isAvailable + ? [{icon: , service: 'jira', label: 'Jira', Component: JiraIntegrationPanel}] + : []), + ...(hasGCal + ? [ + { + icon: , + service: 'gcal', + label: 'Google Calendar', + Component: GCalIntegrationPanel + } + ] + : []) ] as const const {Component} = baseTabs[activeIdx]! diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/AtlassianProviderRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/AtlassianProviderRow.tsx index f9cbb9c06b5..114a8293149 100644 --- a/packages/client/modules/teamDashboard/components/ProviderRow/AtlassianProviderRow.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderRow/AtlassianProviderRow.tsx @@ -86,6 +86,8 @@ const AtlassianProviderRow = (props: Props) => { return message }, [error]) + if (!AtlassianClientManager.isAvailable) return null + return ( <> { } const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT) + if (!GitHubClientManager.isAvailable) return null + return ( <> { const {integrations} = teamMember! const {msTeams} = integrations const {auth} = msTeams + + if (window.__ACTION__.msTeamsDisabled) return null + return ( <> { const {mattermost} = integrations const {auth} = mattermost + if (window.__ACTION__.mattermostDisabled) return null + return ( <> { } const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT) + if (!SlackClientManager.isAvailable) return null + return ( <> { const {cardNumberRef, orgId} = props const stripe = useStripe() const elements = useElements() - const [isLoading, setIsLoading] = useState(false) const atmosphere = useAtmosphere() - const {onError, onCompleted} = useMutationProps() - const [errorMsg, setErrorMsg] = useState() + const {onError, onCompleted, submitMutation, submitting, error} = useMutationProps() const [hasStarted, setHasStarted] = useState(false) const [cardNumberError, setCardNumberError] = useState() const [expiryDateError, setExpiryDateError] = useState() @@ -94,22 +92,16 @@ const BillingForm = (props: Props) => { !cardNumberError && !expiryDateError && !cvcError - const isUpgradeDisabled = isLoading || !stripe || !elements || !hasValidCCDetails + const isUpgradeDisabled = submitting || !stripe || !elements || !hasValidCCDetails const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!stripe || !elements) return - setIsLoading(true) - if (errorMsg) { - setIsLoading(false) - setErrorMsg(null) - return - } + submitMutation() + const cardElement = elements.getElement(CardNumberElement) if (!cardElement) { - setIsLoading(false) - const newErrorMsg = 'Something went wrong. Please try again.' - setErrorMsg(newErrorMsg) + onError(new Error('Something went wrong. Please try again.')) return } const {paymentMethod, error} = await stripe.createPaymentMethod({ @@ -117,8 +109,7 @@ const BillingForm = (props: Props) => { card: cardElement }) if (error) { - setErrorMsg(error.message) - setIsLoading(false) + onError(new Error(error.message)) return } @@ -130,14 +121,12 @@ const BillingForm = (props: Props) => { const newErrMsg = createStripeSubscription.error?.message ?? 'Something went wrong. Please try again or contact support.' - setIsLoading(false) - setErrorMsg(newErrMsg) + onError(new Error(newErrMsg)) return } const {error} = await stripe.confirmCardPayment(stripeSubscriptionClientSecret) if (error) { - setErrorMsg(error.message) - setIsLoading(false) + onError(new Error(error.message)) return } commitLocalUpdate(atmosphere, (store) => { @@ -157,7 +146,7 @@ const BillingForm = (props: Props) => { const handleChange = (type: 'CardNumber' | 'ExpiryDate' | 'CVC') => (event: StripeElementChangeEvent) => { - if (errorMsg) setErrorMsg(null) + if (error) onCompleted() if (!hasStarted && !event.empty) { SendClientSideEvent(atmosphere, 'Payment Details Started', {orgId}) setHasStarted(true) @@ -237,14 +226,14 @@ const BillingForm = (props: Props) => {
- {errorMsg && {errorMsg}} + {error && {error.message}} - {isLoading ? ( + {submitting ? ( <> Upgrading diff --git a/packages/client/package.json b/packages/client/package.json index 3ed2625617a..d65de7ee356 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.38.0", + "version": "7.38.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/client/types/modules.d.ts b/packages/client/types/modules.d.ts index 5e3bbb80bf0..bd45d5ee333 100644 --- a/packages/client/types/modules.d.ts +++ b/packages/client/types/modules.d.ts @@ -35,6 +35,8 @@ interface Window { github: string google: string googleAnalytics: string + mattermostDisabled: boolean | undefined + msTeamsDisabled: boolean | undefined publicPath: string sentry: string slack: string diff --git a/packages/client/utils/AtlassianClientManager.ts b/packages/client/utils/AtlassianClientManager.ts index 849a15b4dd9..00817341852 100644 --- a/packages/client/utils/AtlassianClientManager.ts +++ b/packages/client/utils/AtlassianClientManager.ts @@ -8,6 +8,8 @@ export const ERROR_POPUP_CLOSED = 'Popup closed before authorization was complet class AtlassianClientManager extends AtlassianManager { fetch = window.fetch.bind(window) + static isAvailable = !!window.__ACTION__.atlassian + static openOAuth( atmosphere: Atmosphere, teamId: string, diff --git a/packages/client/utils/GitHubClientManager.ts b/packages/client/utils/GitHubClientManager.ts index a5851d3d924..e5c90cf15d8 100644 --- a/packages/client/utils/GitHubClientManager.ts +++ b/packages/client/utils/GitHubClientManager.ts @@ -8,6 +8,7 @@ class GitHubClientManager { static SCOPE = Providers.GITHUB_SCOPE fetch = window.fetch.bind(window) + static isAvailable = !!window.__ACTION__.github static openOAuth(atmosphere: Atmosphere, teamId: string, mutationProps: MenuMutationProps) { const {submitting, onError, onCompleted, submitMutation} = mutationProps const hash = Math.random().toString(36).substring(5) diff --git a/packages/client/utils/SlackClientManager.ts b/packages/client/utils/SlackClientManager.ts index 3cdaafab127..e3584f1a198 100644 --- a/packages/client/utils/SlackClientManager.ts +++ b/packages/client/utils/SlackClientManager.ts @@ -6,6 +6,7 @@ import SlackManager from './SlackManager' class SlackClientManager extends SlackManager { fetch = window.fetch.bind(window) + static isAvailable = !!window.__ACTION__.slack static openOAuth(atmosphere: Atmosphere, teamId: string, mutationProps: MenuMutationProps) { const {submitting, onError, onCompleted, submitMutation} = mutationProps const hash = Math.random().toString(36).substring(5) diff --git a/packages/embedder/indexing/orgIdsWithFeatureFlag.ts b/packages/embedder/indexing/orgIdsWithFeatureFlag.ts deleted file mode 100644 index 82d86702e67..00000000000 --- a/packages/embedder/indexing/orgIdsWithFeatureFlag.ts +++ /dev/null @@ -1,15 +0,0 @@ -import getRethink from 'parabol-server/database/rethinkDriver' -import {RDatum} from 'parabol-server/database/stricterR' - -export const orgIdsWithFeatureFlag = async () => { - // I had to add a secondary index to the Organization table to get - // this query to be cheap - const r = await getRethink() - return await r - .table('Organization') - .getAll('relatedDiscussions', {index: 'featureFlagsIndex' as any}) - .filter((r: RDatum) => r('featureFlags').contains('relatedDiscussions')) - .map((r: RDatum) => r('id')) - .coerceTo('array') - .run() -} diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 394998ad4b6..0ec59146240 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.0", + "version": "7.38.1", "description": "A service that computes embedding vectors from Parabol objects", "author": "Jordan Husney ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/embedder#readme", diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index e81c37e3e06..c3f2dd4dc89 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.0", + "version": "7.38.1", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -27,8 +27,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.38.0", - "parabol-server": "7.38.0", + "parabol-client": "7.38.1", + "parabol-server": "7.38.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 919495c659a..3ae4007d8e2 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -2,7 +2,7 @@ "name": "integration-tests", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.38.0", + "version": "7.38.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/billing/helpers/adjustUserCount.ts b/packages/server/billing/helpers/adjustUserCount.ts index 42618bd4261..649f568f161 100644 --- a/packages/server/billing/helpers/adjustUserCount.ts +++ b/packages/server/billing/helpers/adjustUserCount.ts @@ -1,9 +1,10 @@ import {InvoiceItemType} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' -import Organization from '../../database/types/Organization' import OrganizationUser from '../../database/types/OrganizationUser' import {DataLoaderWorker} from '../../graphql/graphql' +import isValid from '../../graphql/isValid' +import getKysely from '../../postgres/getKysely' import insertOrgUserAudit from '../../postgres/helpers/insertOrgUserAudit' import {OrganizationUserAuditEventTypeEnum} from '../../postgres/queries/generated/insertOrgUserAuditQuery' import {getUserById} from '../../postgres/queries/getUsersByIds' @@ -22,7 +23,7 @@ const maybeUpdateOrganizationActiveDomain = async ( dataLoader: DataLoaderWorker ) => { const r = await getRethink() - const organization = await r.table('Organization').get(orgId).run() + const organization = await dataLoader.get('organizations').load(orgId) const {isActiveDomainTouched, activeDomain} = organization // don't modify if the domain was set manually if (isActiveDomainTouched) return @@ -38,14 +39,18 @@ const maybeUpdateOrganizationActiveDomain = async ( // don't modify if we can't guess the domain or the domain we guess is the current domain const domain = await getActiveDomainForOrgId(orgId) if (!domain || domain === activeDomain) return - - await r - .table('Organization') - .get(orgId) - .update({ - activeDomain: domain - }) - .run() + organization.activeDomain = domain + const pg = getKysely() + await Promise.all([ + pg.updateTable('Organization').set({activeDomain: domain}).where('id', '=', orgId).execute(), + r + .table('Organization') + .get(orgId) + .update({ + activeDomain: domain + }) + .run() + ]) } const changePause = (inactive: boolean) => async (_orgIds: string[], user: IUser) => { @@ -76,18 +81,16 @@ const changePause = (inactive: boolean) => async (_orgIds: string[], user: IUser const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWorker) => { const {id: userId} = user const r = await getRethink() - const {organizations, organizationUsers} = await r({ - organizationUsers: r + const [rawOrganizations, organizationUsers] = await Promise.all([ + dataLoader.get('organizations').loadMany(orgIds), + r .table('OrganizationUser') .getAll(userId, {index: 'userId'}) .orderBy(r.desc('newUserUntil')) - .coerceTo('array') as unknown as OrganizationUser[], - organizations: r - .table('Organization') - .getAll(r.args(orgIds)) - .coerceTo('array') as unknown as Organization[] - }).run() - + .coerceTo('array') + .run() + ]) + const organizations = rawOrganizations.filter(isValid) const docs = orgIds.map((orgId) => { const oldOrganizationUser = organizationUsers.find( (organizationUser) => organizationUser.orgId === orgId @@ -153,7 +156,6 @@ export default async function adjustUserCount( type: InvoiceItemType, dataLoader: DataLoaderWorker ) { - const r = await getRethink() const orgIds = Array.isArray(orgInput) ? orgInput : [orgInput] const user = (await getUserById(userId))! @@ -164,11 +166,8 @@ export default async function adjustUserCount( const auditEventType = auditEventTypeLookup[type] await insertOrgUserAudit(orgIds, userId, auditEventType) - const paidOrgs = await r - .table('Organization') - .getAll(r.args(orgIds), {index: 'id'}) - .filter((org: RDatum) => org('stripeSubscriptionId').default(null).ne(null)) - .run() + const organizations = await dataLoader.get('organizations').loadMany(orgIds) + const paidOrgs = organizations.filter(isValid).filter((org) => org.stripeSubscriptionId) handleEnterpriseOrgQuantityChanges(paidOrgs, dataLoader).catch() handleTeamOrgQuantityChanges(paidOrgs).catch(Logger.error) diff --git a/packages/server/billing/helpers/generateInvoice.ts b/packages/server/billing/helpers/generateInvoice.ts index 3785a7d6303..1b75d4a6537 100644 --- a/packages/server/billing/helpers/generateInvoice.ts +++ b/packages/server/billing/helpers/generateInvoice.ts @@ -8,7 +8,6 @@ import {InvoiceLineItemEnum} from '../../database/types/InvoiceLineItem' import InvoiceLineItemDetail from '../../database/types/InvoiceLineItemDetail' import InvoiceLineItemOtherAdjustments from '../../database/types/InvoiceLineItemOtherAdjustments' import NextPeriodCharges from '../../database/types/NextPeriodCharges' -import Organization from '../../database/types/Organization' import QuantityChangeLineItem from '../../database/types/QuantityChangeLineItem' import generateUID from '../../generateUID' import {DataLoaderWorker} from '../../graphql/graphql' @@ -354,16 +353,16 @@ export default async function generateInvoice( invoice.status === 'paid' && invoice.status_transitions.paid_at ? fromEpochSeconds(invoice.status_transitions.paid_at) : undefined - - const {organization, billingLeaderIds} = await r({ - organization: r.table('Organization').get(orgId) as unknown as Organization, - billingLeaderIds: r + const [organization, billingLeaderIds] = await Promise.all([ + dataLoader.get('organizations').load(orgId), + r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) .filter({removedAt: null}) .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role'))) - .coerceTo('array')('userId') as unknown as string[] - }).run() + .coerceTo('array')('userId') + .run() as any as string[] + ]) const billingLeaders = (await dataLoader.get('users').loadMany(billingLeaderIds)).filter(isValid) const billingLeaderEmails = billingLeaders.map((user) => user.email) diff --git a/packages/server/billing/helpers/generateUpcomingInvoice.ts b/packages/server/billing/helpers/generateUpcomingInvoice.ts index 189ab930a18..7980c3871df 100644 --- a/packages/server/billing/helpers/generateUpcomingInvoice.ts +++ b/packages/server/billing/helpers/generateUpcomingInvoice.ts @@ -1,4 +1,3 @@ -import getRethink from '../../database/rethinkDriver' import {DataLoaderWorker} from '../../graphql/graphql' import getUpcomingInvoiceId from '../../utils/getUpcomingInvoiceId' import {getStripeManager} from '../../utils/stripe' @@ -6,9 +5,9 @@ import fetchAllLines from './fetchAllLines' import generateInvoice from './generateInvoice' const generateUpcomingInvoice = async (orgId: string, dataLoader: DataLoaderWorker) => { - const r = await getRethink() const invoiceId = getUpcomingInvoiceId(orgId) - const {stripeId} = await r.table('Organization').get(orgId).pluck('stripeId').run() + const organization = await dataLoader.get('organizations').load(orgId) + const {stripeId} = organization const manager = getStripeManager() const [stripeLineItems, upcomingInvoice] = await Promise.all([ fetchAllLines('upcoming', stripeId), diff --git a/packages/server/billing/helpers/teamLimitsCheck.ts b/packages/server/billing/helpers/teamLimitsCheck.ts index 31103db959b..fff842326db 100644 --- a/packages/server/billing/helpers/teamLimitsCheck.ts +++ b/packages/server/billing/helpers/teamLimitsCheck.ts @@ -80,7 +80,13 @@ export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoa if (!(await isLimitExceeded(orgId))) { const billingLeadersIds = await dataLoader.get('billingLeadersIdsByOrgId').load(orgId) + const pg = getKysely() await Promise.all([ + pg + .updateTable('Organization') + .set({tierLimitExceededAt: null, scheduledLockAt: null, lockedAt: null}) + .where('id', '=', orgId) + .execute(), r .table('Organization') .get(orgId) @@ -129,16 +135,26 @@ export const checkTeamsLimit = async (orgId: string, dataLoader: DataLoaderWorke const now = new Date() const scheduledLockAt = new Date(now.getTime() + ms(`${Threshold.STARTER_TIER_LOCK_AFTER_DAYS}d`)) - - await r - .table('Organization') - .get(orgId) - .update({ - tierLimitExceededAt: now, - scheduledLockAt, - updatedAt: now - }) - .run() + const pg = getKysely() + await Promise.all([ + pg + .updateTable('Organization') + .set({ + tierLimitExceededAt: now, + scheduledLockAt + }) + .where('id', '=', orgId) + .execute(), + r + .table('Organization') + .get(orgId) + .update({ + tierLimitExceededAt: now, + scheduledLockAt, + updatedAt: now + }) + .run() + ]) dataLoader.get('organizations').clear(orgId) const billingLeaders = await getBillingLeadersByOrgId(orgId, dataLoader) diff --git a/packages/server/billing/helpers/terminateSubscription.ts b/packages/server/billing/helpers/terminateSubscription.ts index 0b1c6d3f4c5..1850a614828 100644 --- a/packages/server/billing/helpers/terminateSubscription.ts +++ b/packages/server/billing/helpers/terminateSubscription.ts @@ -1,31 +1,45 @@ import getRethink from '../../database/rethinkDriver' import Organization from '../../database/types/Organization' +import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' +import sendToSentry from '../../utils/sendToSentry' import {getStripeManager} from '../../utils/stripe' const terminateSubscription = async (orgId: string) => { const r = await getRethink() + const pg = getKysely() const now = new Date() // flag teams as unpaid - const [rethinkResult] = await Promise.all([ - r({ - organization: r - .table('Organization') - .get(orgId) - .update( - { - // periodEnd should always be redundant, but useful for testing purposes - periodEnd: now, - stripeSubscriptionId: null - }, - {returnChanges: true} - )('changes')(0)('old_val') - .default(null) as unknown as Organization - }).run() + const [pgOrganization, organization] = await Promise.all([ + pg + .with('OldOrg', (qc) => + qc.selectFrom('Organization').select('stripeSubscriptionId').where('id', '=', orgId) + ) + .updateTable('Organization') + .set({periodEnd: now, stripeSubscriptionId: null}) + .where('id', '=', orgId) + .returning((qc) => + qc.selectFrom('OldOrg').select('stripeSubscriptionId').as('stripeSubscriptionId') + ) + .executeTakeFirst(), + r + .table('Organization') + .get(orgId) + .update( + { + // periodEnd should always be redundant, but useful for testing purposes + periodEnd: now, + stripeSubscriptionId: null + }, + {returnChanges: true} + )('changes')(0)('old_val') + .default(null) + .run() as unknown as Organization ]) - const {organization} = rethinkResult const {stripeSubscriptionId} = organization - + if (stripeSubscriptionId !== pgOrganization?.stripeSubscriptionId) { + sendToSentry(new Error(`stripeSubscriptionId mismatch for orgId ${orgId}`)) + } if (stripeSubscriptionId) { const manager = getStripeManager() try { diff --git a/packages/server/billing/stripeWebhookHandler.ts b/packages/server/billing/stripeWebhookHandler.ts index dff889d7399..4acd744c486 100644 --- a/packages/server/billing/stripeWebhookHandler.ts +++ b/packages/server/billing/stripeWebhookHandler.ts @@ -82,17 +82,6 @@ const eventLookup = { } }, subscription: { - updated: { - getVars: ({customer, id}: {customer: string; id: string}) => ({ - customerId: customer, - subscriptionId: id - }), - query: ` - mutation StripeUpdateSubscription($customerId: ID!, $subscriptionId: ID!) { - stripeUpdateSubscription(customerId: $customerId, subscriptionId: $subscriptionId) - } - ` - }, created: { getVars: ({customer, id}: {customer: string; id: string}) => ({ customerId: customer, @@ -100,7 +89,7 @@ const eventLookup = { }), query: ` mutation StripeCreateSubscription($customerId: ID!, $subscriptionId: ID!) { - stripeUpdateSubscription(customerId: $customerId, subscriptionId: $subscriptionId) + stripeCreateSubscription(customerId: $customerId, subscriptionId: $subscriptionId) } ` }, diff --git a/packages/server/database/types/GoogleAnalyzedEntity.ts b/packages/server/database/types/GoogleAnalyzedEntity.ts index e66ce25f7c3..b148d69967e 100644 --- a/packages/server/database/types/GoogleAnalyzedEntity.ts +++ b/packages/server/database/types/GoogleAnalyzedEntity.ts @@ -1,5 +1,3 @@ -import {sql} from 'kysely' - interface Input { lemma?: string name: string @@ -17,8 +15,3 @@ export default class GoogleAnalyzedEntity { this.salience = salience } } - -export const toGoogleAnalyzedEntityPG = (entities: GoogleAnalyzedEntity[]) => - sql< - string[] - >`(select coalesce(array_agg((name, salience, lemma)::"GoogleAnalyzedEntity"), '{}') from json_populate_recordset(null::"GoogleAnalyzedEntity", ${JSON.stringify(entities)}))` diff --git a/packages/server/database/types/processTeamsLimitsJob.ts b/packages/server/database/types/processTeamsLimitsJob.ts index 57beb49c170..562a9698028 100644 --- a/packages/server/database/types/processTeamsLimitsJob.ts +++ b/packages/server/database/types/processTeamsLimitsJob.ts @@ -3,6 +3,7 @@ import sendTeamsLimitEmail from '../../billing/helpers/sendTeamsLimitEmail' import {DataLoaderWorker} from '../../graphql/graphql' import isValid from '../../graphql/isValid' import publishNotification from '../../graphql/public/mutations/helpers/publishNotification' +import getKysely from '../../postgres/getKysely' import NotificationTeamsLimitReminder from './NotificationTeamsLimitReminder' import ScheduledTeamLimitsJob from './ScheduledTeamLimitsJob' @@ -27,7 +28,14 @@ const processTeamsLimitsJob = async (job: ScheduledTeamLimitsJob, dataLoader: Da if (type === 'LOCK_ORGANIZATION') { const now = new Date() - await r.table('Organization').get(orgId).update({lockedAt: now}).run() + await Promise.all([ + getKysely() + .updateTable('Organization') + .set({lockedAt: now}) + .where('id', '=', 'orgId') + .execute(), + r.table('Organization').get(orgId).update({lockedAt: now}).run() + ]) organization.lockedAt = lockedAt } else if (type === 'WARN_ORGANIZATION') { const notificationsToInsert = billingLeadersIds.map((userId) => { diff --git a/packages/server/graphql/mutations/addOrg.ts b/packages/server/graphql/mutations/addOrg.ts index d3c9ca3df03..d6ce8215734 100644 --- a/packages/server/graphql/mutations/addOrg.ts +++ b/packages/server/graphql/mutations/addOrg.ts @@ -62,7 +62,11 @@ export default { const teamId = generateUID() const {email} = viewer await createNewOrg(orgId, orgName, viewerId, email, dataLoader) - await createTeamAndLeader(viewer, {id: teamId, orgId, isOnboardTeam: false, ...newTeam}) + await createTeamAndLeader( + viewer, + {id: teamId, orgId, isOnboardTeam: false, ...newTeam}, + dataLoader + ) const {tms} = authToken // MUTATIVE diff --git a/packages/server/graphql/mutations/addTeam.ts b/packages/server/graphql/mutations/addTeam.ts index 132926cd46c..3b9fb4f5bdb 100644 --- a/packages/server/graphql/mutations/addTeam.ts +++ b/packages/server/graphql/mutations/addTeam.ts @@ -85,7 +85,7 @@ export default { // RESOLUTION const teamId = generateUID() - await createTeamAndLeader(viewer, {id: teamId, isOnboardTeam: false, ...newTeam}) + await createTeamAndLeader(viewer, {id: teamId, isOnboardTeam: false, ...newTeam}, dataLoader) const {tms} = authToken // MUTATIVE diff --git a/packages/server/graphql/mutations/createReflection.ts b/packages/server/graphql/mutations/createReflection.ts index 43b55797825..de7171d3705 100644 --- a/packages/server/graphql/mutations/createReflection.ts +++ b/packages/server/graphql/mutations/createReflection.ts @@ -6,10 +6,10 @@ import getGroupSmartTitle from 'parabol-client/utils/smartGroup/getGroupSmartTit import unlockAllStagesForPhase from 'parabol-client/utils/unlockAllStagesForPhase' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import getRethink from '../../database/rethinkDriver' -import {toGoogleAnalyzedEntityPG} from '../../database/types/GoogleAnalyzedEntity' import ReflectionGroup from '../../database/types/ReflectionGroup' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' +import {toGoogleAnalyzedEntity} from '../../postgres/helpers/toGoogleAnalyzedEntity' import {analytics} from '../../utils/analytics/analytics' import {getUserId} from '../../utils/authorization' import publish from '../../utils/publish' @@ -102,7 +102,7 @@ export default { await pg .with('Group', (qc) => qc.insertInto('RetroReflectionGroup').values(reflectionGroup)) .insertInto('RetroReflection') - .values({...reflection, entities: toGoogleAnalyzedEntityPG(entities)}) + .values({...reflection, entities: toGoogleAnalyzedEntity(entities)}) .execute() const groupPhase = phases.find((phase) => phase.phaseType === 'group')! diff --git a/packages/server/graphql/mutations/downgradeToStarter.ts b/packages/server/graphql/mutations/downgradeToStarter.ts index 90f948e3d0c..bbed44fd08c 100644 --- a/packages/server/graphql/mutations/downgradeToStarter.ts +++ b/packages/server/graphql/mutations/downgradeToStarter.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLList, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import {getUserId, isSuperUser, isUserBillingLeader} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -37,7 +36,6 @@ export default { }: {orgId: string; reasonsForLeaving?: TReasonToDowngradeEnum[]; otherTool?: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -56,7 +54,8 @@ export default { return standardError(new Error('Other tool name is too long'), {userId: viewerId}) } - const {stripeSubscriptionId, tier} = await r.table('Organization').get(orgId).run() + const {stripeSubscriptionId, tier} = await dataLoader.get('organizations').load(orgId) + dataLoader.get('organizations').clear(orgId) if (tier === 'starter') { return standardError(new Error('Already on free tier'), {userId: viewerId}) @@ -68,6 +67,7 @@ export default { orgId, stripeSubscriptionId!, viewer, + dataLoader, reasonsForLeaving, otherTool ) diff --git a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts index 9fb461504f6..8d1d09d3cbd 100644 --- a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts +++ b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts @@ -128,7 +128,7 @@ const bootstrapNewUser = async ( const orgName = `${newUser.preferredName}’s Org` await createNewOrg(orgId, orgName, userId, email, dataLoader) await Promise.all([ - createTeamAndLeader(newUser as IUser, validNewTeam), + createTeamAndLeader(newUser as IUser, validNewTeam, dataLoader), addSeedTasks(userId, teamId), r.table('SuggestedAction').insert(new SuggestedActionInviteYourTeam({userId, teamId})).run(), sendPromptToJoinOrg(newUser, dataLoader) diff --git a/packages/server/graphql/mutations/helpers/createNewOrg.ts b/packages/server/graphql/mutations/helpers/createNewOrg.ts index 26eee3a436c..b5fa95ea827 100644 --- a/packages/server/graphql/mutations/helpers/createNewOrg.ts +++ b/packages/server/graphql/mutations/helpers/createNewOrg.ts @@ -1,6 +1,7 @@ import getRethink from '../../../database/rethinkDriver' import Organization from '../../../database/types/Organization' import OrganizationUser from '../../../database/types/OrganizationUser' +import getKysely from '../../../postgres/getKysely' import insertOrgUserAudit from '../../../postgres/helpers/insertOrgUserAudit' import getDomainFromEmail from '../../../utils/getDomainFromEmail' import {DataLoaderWorker} from '../../graphql' @@ -28,6 +29,10 @@ export default async function createNewOrg( tier: org.tier }) await insertOrgUserAudit([orgId], leaderUserId, 'added') + await getKysely() + .insertInto('Organization') + .values({...org, creditCard: null}) + .execute() return r({ org: r.table('Organization').insert(org), organizationUser: r.table('OrganizationUser').insert(orgUser) diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index f76a2540d27..1019dd0b789 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -4,6 +4,7 @@ import MeetingSettingsPoker from '../../../database/types/MeetingSettingsPoker' import MeetingSettingsRetrospective from '../../../database/types/MeetingSettingsRetrospective' import Team from '../../../database/types/Team' import TimelineEventCreatedTeam from '../../../database/types/TimelineEventCreatedTeam' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import IUser from '../../../postgres/types/IUser' import addTeamIdToTMS from '../../../safeMutations/addTeamIdToTMS' @@ -17,11 +18,15 @@ interface ValidNewTeam { } // used for addorg, addTeam -export default async function createTeamAndLeader(user: IUser, newTeam: ValidNewTeam) { +export default async function createTeamAndLeader( + user: IUser, + newTeam: ValidNewTeam, + dataLoader: DataLoaderInstance +) { const r = await getRethink() const {id: userId} = user const {id: teamId, orgId} = newTeam - const organization = await r.table('Organization').get(orgId).run() + const organization = await dataLoader.get('organizations').load(orgId) const {tier, trialStartDate} = organization const verifiedTeam = new Team({...newTeam, createdBy: userId, tier, trialStartDate}) const meetingSettings = [ diff --git a/packages/server/graphql/mutations/helpers/hideConversionModal.ts b/packages/server/graphql/mutations/helpers/hideConversionModal.ts index c71115516d5..3e186459c44 100644 --- a/packages/server/graphql/mutations/helpers/hideConversionModal.ts +++ b/packages/server/graphql/mutations/helpers/hideConversionModal.ts @@ -1,4 +1,5 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import errorFilter from '../../errorFilter' import {DataLoaderWorker} from '../../graphql' @@ -7,6 +8,11 @@ const hideConversionModal = async (orgId: string, dataLoader: DataLoaderWorker) const {showConversionModal} = organization if (showConversionModal) { const r = await getRethink() + await getKysely() + .updateTable('Organization') + .set({showConversionModal: false}) + .where('id', '=', orgId) + .execute() await r .table('Organization') .get(orgId) diff --git a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts index 361416e52ab..5e998500973 100644 --- a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts +++ b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts @@ -33,7 +33,7 @@ const oldUpgradeToTeamTier = async ( const customer = stripeId ? await manager.updatePayment(stripeId, source) : await manager.createCustomer(orgId, email, undefined, source) - + if (customer instanceof Error) throw customer let subscriptionFields = {} if (!stripeSubscriptionId) { const subscription = await manager.createTeamSubscriptionOld(customer.id, orgId, quantity) diff --git a/packages/server/graphql/mutations/helpers/removeFromOrg.ts b/packages/server/graphql/mutations/helpers/removeFromOrg.ts index 945fc3d4a83..ac4fb4ccca5 100644 --- a/packages/server/graphql/mutations/helpers/removeFromOrg.ts +++ b/packages/server/graphql/mutations/helpers/removeFromOrg.ts @@ -57,7 +57,7 @@ const removeFromOrg = async ( // need to make sure the org doc is updated before adjusting this const {role} = organizationUser if (role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role)) { - const organization = await r.table('Organization').get(orgId).run() + const organization = await dataLoader.get('organizations').load(orgId) // if no other billing leader, promote the oldest // if team tier & no other member, downgrade to starter const otherBillingLeaders = await r @@ -84,7 +84,7 @@ const removeFromOrg = async ( }) .run() } else if (organization.tier !== 'starter') { - await resolveDowngradeToStarter(orgId, organization.stripeSubscriptionId!, user) + await resolveDowngradeToStarter(orgId, organization.stripeSubscriptionId!, user, dataLoader) } } } diff --git a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts index ce82907ecfb..803c767e1e7 100644 --- a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts +++ b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts @@ -1,5 +1,5 @@ import getRethink from '../../../database/rethinkDriver' -import Organization from '../../../database/types/Organization' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import updateTeamByOrgId from '../../../postgres/queries/updateTeamByOrgId' import {analytics} from '../../../utils/analytics/analytics' @@ -13,6 +13,7 @@ const resolveDowngradeToStarter = async ( orgId: string, stripeSubscriptionId: string, user: {id: string; email: string}, + dataLoader: DataLoaderInstance, reasonsForLeaving?: ReasonToDowngradeEnum[], otherTool?: string ) => { @@ -27,7 +28,16 @@ const resolveDowngradeToStarter = async ( } const [org] = await Promise.all([ - r.table('Organization').get(orgId).run() as unknown as Organization, + dataLoader.get('organizations').load(orgId), + pg + .updateTable('Organization') + .set({ + tier: 'starter', + periodEnd: now, + stripeSubscriptionId: null + }) + .where('id', '=', orgId) + .execute(), pg .updateTable('SAML') .set({metadata: null, lastUpdatedBy: user.id}) @@ -49,7 +59,7 @@ const resolveDowngradeToStarter = async ( orgId ) ]) - + dataLoader.get('organizations').clear(orgId) await Promise.all([setUserTierForOrgId(orgId), setTierForOrgUsers(orgId)]) analytics.organizationDowngraded(user, { orgId, diff --git a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts index 7e49609a4df..c803811cc56 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts @@ -34,7 +34,7 @@ const safeCreateRetrospective = async ( dataLoader.get('teams').loadNonNull(teamId) ]) - const organization = await r.table('Organization').get(team.orgId).run() + const organization = await dataLoader.get('organizations').load(team.orgId) const {showConversionModal} = organization const meetingId = generateUID() diff --git a/packages/server/graphql/mutations/moveTeamToOrg.ts b/packages/server/graphql/mutations/moveTeamToOrg.ts index 18196676f41..bef33207dd1 100644 --- a/packages/server/graphql/mutations/moveTeamToOrg.ts +++ b/packages/server/graphql/mutations/moveTeamToOrg.ts @@ -30,7 +30,7 @@ const moveToOrg = async ( const su = isSuperUser(authToken) // VALIDATION const [org, teams, isPaidResult] = await Promise.all([ - r.table('Organization').get(orgId).run(), + dataLoader.get('organizations').load(orgId), getTeamsByIds([teamId]), pg .selectFrom('Team') @@ -117,7 +117,7 @@ const moveToOrg = async ( const {newToOrgUserIds} = rethinkResult // if no teams remain on the org, remove it - await safeArchiveEmptyStarterOrganization(currentOrgId) + await safeArchiveEmptyStarterOrganization(currentOrgId, dataLoader) await Promise.all( newToOrgUserIds.map((newUserId) => { diff --git a/packages/server/graphql/mutations/payLater.ts b/packages/server/graphql/mutations/payLater.ts index 54af091b0d2..49a4b6ae794 100644 --- a/packages/server/graphql/mutations/payLater.ts +++ b/packages/server/graphql/mutations/payLater.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' +import getKysely from '../../postgres/getKysely' import getPg from '../../postgres/getPg' import {incrementUserPayLaterClickCountQuery} from '../../postgres/queries/generated/incrementUserPayLaterClickCountQuery' import {analytics} from '../../utils/analytics/analytics' @@ -49,6 +50,13 @@ export default { // RESOLUTION const team = await dataLoader.get('teams').loadNonNull(teamId) const {orgId} = team + await getKysely() + .updateTable('Organization') + .set((eb) => ({ + payLaterClickCount: eb('payLaterClickCount', '+', 1) + })) + .where('id', '=', orgId) + .execute() await r .table('Organization') .get(orgId) diff --git a/packages/server/graphql/mutations/updateReflectionContent.ts b/packages/server/graphql/mutations/updateReflectionContent.ts index c716f776884..36b79bfb958 100644 --- a/packages/server/graphql/mutations/updateReflectionContent.ts +++ b/packages/server/graphql/mutations/updateReflectionContent.ts @@ -5,8 +5,8 @@ import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import getGroupSmartTitle from 'parabol-client/utils/smartGroup/getGroupSmartTitle' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import stringSimilarity from 'string-similarity' -import {toGoogleAnalyzedEntityPG} from '../../database/types/GoogleAnalyzedEntity' import getKysely from '../../postgres/getKysely' +import {toGoogleAnalyzedEntity} from '../../postgres/helpers/toGoogleAnalyzedEntity' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -88,7 +88,7 @@ export default { .updateTable('RetroReflection') .set({ content: normalizedContent, - entities: toGoogleAnalyzedEntityPG(entities), + entities: toGoogleAnalyzedEntity(entities), sentimentScore, plaintextContent }) diff --git a/packages/server/graphql/private/mutations/changeEmailDomain.ts b/packages/server/graphql/private/mutations/changeEmailDomain.ts index dab120dd709..351bbe85af4 100644 --- a/packages/server/graphql/private/mutations/changeEmailDomain.ts +++ b/packages/server/graphql/private/mutations/changeEmailDomain.ts @@ -55,6 +55,11 @@ const changeEmailDomain: MutationResolvers['changeEmailDomain'] = async ( }) .where('domain', 'like', normalizedOldDomain) .execute(), + pg + .updateTable('Organization') + .set({activeDomain: normalizedNewDomain}) + .where('activeDomain', '=', normalizedOldDomain) + .execute(), r .table('Organization') .filter((row: RDatum) => row('activeDomain').eq(normalizedOldDomain)) diff --git a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts index aa0ea60f306..ab2da48431c 100644 --- a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts +++ b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts @@ -101,6 +101,12 @@ const draftEnterpriseInvoice: MutationResolvers['draftEnterpriseInvoice'] = asyn if (!stripeId) { // create the customer const customer = await manager.createCustomer(orgId, apEmail || user.email) + if (customer instanceof Error) throw customer + await getKysely() + .updateTable('Organization') + .set({stripeId: customer.id}) + .where('id', '=', orgId) + .execute() await r.table('Organization').get(orgId).update({stripeId: customer.id}).run() customerId = customer.id } else { @@ -115,6 +121,21 @@ const draftEnterpriseInvoice: MutationResolvers['draftEnterpriseInvoice'] = asyn ) await Promise.all([ + pg + .updateTable('Organization') + .set({ + periodEnd: fromEpochSeconds(subscription.current_period_end), + periodStart: fromEpochSeconds(subscription.current_period_start), + stripeSubscriptionId: subscription.id, + tier: 'enterprise', + tierLimitExceededAt: null, + scheduledLockAt: null, + lockedAt: null, + updatedAt: now, + trialStartDate: null + }) + .where('id', '=', orgId) + .execute(), r({ updatedOrg: r .table('Organization') diff --git a/packages/server/graphql/private/mutations/endTrial.ts b/packages/server/graphql/private/mutations/endTrial.ts index fce952a0eaa..890ffdb0015 100644 --- a/packages/server/graphql/private/mutations/endTrial.ts +++ b/packages/server/graphql/private/mutations/endTrial.ts @@ -19,6 +19,7 @@ const endTrial: MutationResolvers['endTrial'] = async (_source, {orgId}, {dataLo // RESOLUTION await Promise.all([ + pg.updateTable('Organization').set({trialStartDate: null}).where('id', '=', orgId).execute(), r({ orgUpdate: r.table('Organization').get(orgId).update({ trialStartDate: null, diff --git a/packages/server/graphql/private/mutations/flagConversionModal.ts b/packages/server/graphql/private/mutations/flagConversionModal.ts index b46548d4e92..4ba7bd4b4f1 100644 --- a/packages/server/graphql/private/mutations/flagConversionModal.ts +++ b/packages/server/graphql/private/mutations/flagConversionModal.ts @@ -1,19 +1,27 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {MutationResolvers} from '../resolverTypes' const flagConversionModal: MutationResolvers['flagConversionModal'] = async ( _source, - {active, orgId} + {active, orgId}, + {dataLoader} ) => { const r = await getRethink() // VALIDATION - const organization = await r.table('Organization').get(orgId).run() + const organization = await dataLoader.get('organizations').load(orgId) if (!organization) { return {error: {message: 'Invalid orgId'}} } // RESOLUTION + organization.showConversionModal = active + await getKysely() + .updateTable('Organization') + .set({showConversionModal: active}) + .where('id', '=', orgId) + .execute() await r .table('Organization') .get(orgId) diff --git a/packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts b/packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts index 01642c464df..d49383b0486 100644 --- a/packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts +++ b/packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts @@ -8,6 +8,7 @@ import getRethink from '../../../database/rethinkDriver' import {RDatum, RValue} from '../../../database/stricterR' import UpcomingInvoiceEmailTemplate from '../../../email/UpcomingInvoiceEmailTemplate' import getMailManager from '../../../email/getMailManager' +import getKysely from '../../../postgres/getKysely' import IUser from '../../../postgres/types/IUser' import {MutationResolvers} from '../resolverTypes' @@ -134,6 +135,11 @@ const sendUpcomingInvoiceEmails: MutationResolvers['sendUpcomingInvoiceEmails'] }) ) const orgIds = organizations.map(({id}) => id) + await getKysely() + .updateTable('Organization') + .set({upcomingInvoiceEmailSentAt: now}) + .where('id', 'in', orgIds) + .execute() await r .table('Organization') .getAll(r.args(orgIds)) diff --git a/packages/server/graphql/private/mutations/setOrganizationDomain.ts b/packages/server/graphql/private/mutations/setOrganizationDomain.ts index cefc6fa5274..b374ed7ce35 100644 --- a/packages/server/graphql/private/mutations/setOrganizationDomain.ts +++ b/packages/server/graphql/private/mutations/setOrganizationDomain.ts @@ -1,19 +1,26 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {MutationResolvers} from '../resolverTypes' const setOrganizationDomain: MutationResolvers['setOrganizationDomain'] = async ( _source, - {orgId, domain} + {orgId, domain}, + {dataLoader} ) => { const r = await getRethink() // VALIDATION - const organization = await r.table('Organization').get(orgId).run() - + const organization = await dataLoader.get('organizations').load(orgId) + dataLoader.get('organizations').clear(orgId) if (!organization) { throw new Error('Organization not found') } // RESOLUTION + await getKysely() + .updateTable('Organization') + .set({activeDomain: domain, isActiveDomainTouched: true}) + .where('id', '=', orgId) + .execute() await r .table('Organization') .get(orgId) diff --git a/packages/server/graphql/private/mutations/startTrial.ts b/packages/server/graphql/private/mutations/startTrial.ts index af4e52efb61..cff3800cb7b 100644 --- a/packages/server/graphql/private/mutations/startTrial.ts +++ b/packages/server/graphql/private/mutations/startTrial.ts @@ -25,6 +25,11 @@ const startTrial: MutationResolvers['startTrial'] = async (_source, {orgId}, {da // RESOLUTION await Promise.all([ + pg + .updateTable('Organization') + .set({trialStartDate: now, tierLimitExceededAt: null, scheduledLockAt: null, lockedAt: null}) + .where('id', '=', orgId) + .execute(), r({ updatedOrg: r.table('Organization').get(orgId).update({ trialStartDate: now, diff --git a/packages/server/graphql/private/mutations/stripeUpdateSubscription.ts b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts similarity index 57% rename from packages/server/graphql/private/mutations/stripeUpdateSubscription.ts rename to packages/server/graphql/private/mutations/stripeCreateSubscription.ts index 12831a9fab4..4acbd075ce5 100644 --- a/packages/server/graphql/private/mutations/stripeUpdateSubscription.ts +++ b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts @@ -1,9 +1,11 @@ +import Stripe from 'stripe' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {isSuperUser} from '../../../utils/authorization' import {getStripeManager} from '../../../utils/stripe' import {MutationResolvers} from '../resolverTypes' -const stripeUpdateSubscription: MutationResolvers['stripeUpdateSubscription'] = async ( +const stripeCreateSubscription: MutationResolvers['stripeCreateSubscription'] = async ( _source, {customerId, subscriptionId}, {authToken} @@ -28,6 +30,23 @@ const stripeUpdateSubscription: MutationResolvers['stripeUpdateSubscription'] = throw new Error(`orgId not found on metadata for customer ${customerId}`) } + const subscription = await manager.retrieveSubscription(subscriptionId) + const invalidStatuses: Stripe.Subscription.Status[] = [ + 'canceled', + 'incomplete', + 'incomplete_expired' + ] + const isSubscriptionInvalid = invalidStatuses.some((status) => (subscription.status = status)) + if (isSubscriptionInvalid) return false + + await getKysely() + .updateTable('Organization') + .set({ + stripeSubscriptionId: subscriptionId + }) + .where('id', '=', orgId) + .execute() + await r .table('Organization') .get(orgId) @@ -39,4 +58,4 @@ const stripeUpdateSubscription: MutationResolvers['stripeUpdateSubscription'] = return true } -export default stripeUpdateSubscription +export default stripeCreateSubscription diff --git a/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts index b42338b2371..3b8d2dd1f7f 100644 --- a/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts +++ b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts @@ -1,5 +1,6 @@ import getRethink from '../../../database/rethinkDriver' import Organization from '../../../database/types/Organization' +import getKysely from '../../../postgres/getKysely' import {isSuperUser} from '../../../utils/authorization' import {getStripeManager} from '../../../utils/stripe' import {MutationResolvers} from '../resolverTypes' @@ -36,7 +37,11 @@ const stripeDeleteSubscription: MutationResolvers['stripeDeleteSubscription'] = if (stripeSubscriptionId !== subscriptionId) { throw new Error(`Subscription ID does not match: ${stripeSubscriptionId} vs ${subscriptionId}`) } - + await getKysely() + .updateTable('Organization') + .set({stripeSubscriptionId: null}) + .where('id', '=', orgId) + .execute() await r .table('Organization') .get(orgId) diff --git a/packages/server/graphql/private/mutations/stripeInvoiceFinalized.ts b/packages/server/graphql/private/mutations/stripeInvoiceFinalized.ts index 480a6bfef12..6450f9dc57f 100644 --- a/packages/server/graphql/private/mutations/stripeInvoiceFinalized.ts +++ b/packages/server/graphql/private/mutations/stripeInvoiceFinalized.ts @@ -6,7 +6,7 @@ import {MutationResolvers} from '../resolverTypes' const stripeInvoiceFinalized: MutationResolvers['stripeInvoiceFinalized'] = async ( _source, {invoiceId}, - {authToken} + {authToken, dataLoader} ) => { const r = await getRethink() const now = new Date() @@ -29,7 +29,7 @@ const stripeInvoiceFinalized: MutationResolvers['stripeInvoiceFinalized'] = asyn livemode, metadata: {orgId} } = customer - const org = await r.table('Organization').get(orgId).run() + const org = await dataLoader.get('organizations').load(orgId!) if (!org) { if (livemode) { throw new Error( diff --git a/packages/server/graphql/private/mutations/stripeInvoicePaid.ts b/packages/server/graphql/private/mutations/stripeInvoicePaid.ts index 67956661c62..527dd1ec1fa 100644 --- a/packages/server/graphql/private/mutations/stripeInvoicePaid.ts +++ b/packages/server/graphql/private/mutations/stripeInvoicePaid.ts @@ -7,7 +7,7 @@ import {MutationResolvers} from '../resolverTypes' const stripeInvoicePaid: MutationResolvers['stripeInvoicePaid'] = async ( _source, {invoiceId}, - {authToken} + {authToken, dataLoader} ) => { const r = await getRethink() const now = new Date() @@ -30,8 +30,11 @@ const stripeInvoicePaid: MutationResolvers['stripeInvoicePaid'] = async ( livemode, metadata: {orgId} } = stripeCustomer - const org = await r.table('Organization').get(orgId).run() - if (!org || !orgId) { + if (!orgId) { + throw new Error(`Payment cannot succeed. Org ${orgId} does not exist for invoice ${invoiceId}`) + } + const org = await dataLoader.get('organizations').load(orgId) + if (!org) { if (livemode) { throw new Error( `Payment cannot succeed. Org ${orgId} does not exist for invoice ${invoiceId}` diff --git a/packages/server/graphql/private/mutations/stripeSucceedPayment.ts b/packages/server/graphql/private/mutations/stripeSucceedPayment.ts index 30334a7dc7e..03a62264dae 100644 --- a/packages/server/graphql/private/mutations/stripeSucceedPayment.ts +++ b/packages/server/graphql/private/mutations/stripeSucceedPayment.ts @@ -7,7 +7,7 @@ import {MutationResolvers} from '../resolverTypes' const stripeSucceedPayment: MutationResolvers['stripeSucceedPayment'] = async ( _source, {invoiceId}, - {authToken} + {authToken, dataLoader} ) => { const r = await getRethink() const now = new Date() @@ -30,8 +30,11 @@ const stripeSucceedPayment: MutationResolvers['stripeSucceedPayment'] = async ( livemode, metadata: {orgId} } = customer - const org = await r.table('Organization').get(orgId).run() - if (!org || !orgId) { + if (!orgId) { + throw new Error(`Payment cannot succeed. Org ${orgId} does not exist for invoice ${invoiceId}`) + } + const org = await dataLoader.get('organizations').load(orgId) + if (!org) { if (livemode) { throw new Error( `Payment cannot succeed. Org ${orgId} does not exist for invoice ${invoiceId}` diff --git a/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts b/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts index 3fc305757a1..2772c922b30 100644 --- a/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts +++ b/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts @@ -1,4 +1,6 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {toCreditCard} from '../../../postgres/helpers/toCreditCard' import {isSuperUser} from '../../../utils/authorization' import {getStripeManager} from '../../../utils/stripe' import getCCFromCustomer from '../../mutations/helpers/getCCFromCustomer' @@ -23,6 +25,14 @@ const stripeUpdateCreditCard: MutationResolvers['stripeUpdateCreditCard'] = asyn const { metadata: {orgId} } = customer + if (!orgId) { + throw new Error('Unable to update credit card as customer does not have an orgId') + } + await getKysely() + .updateTable('Organization') + .set({creditCard: toCreditCard(creditCard)}) + .where('id', '=', orgId) + .execute() await r.table('Organization').get(orgId).update({creditCard}).run() return true } diff --git a/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts b/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts index 86cede3f0b7..733d03ae10b 100644 --- a/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts +++ b/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts @@ -1,14 +1,18 @@ +import {sql} from 'kysely' import {RValue} from 'rethinkdb-ts' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import isValid from '../../isValid' import {MutationResolvers} from '../resolverTypes' const updateOrgFeatureFlag: MutationResolvers['updateOrgFeatureFlag'] = async ( _source, - {orgIds, flag, addFlag} + {orgIds, flag, addFlag}, + {dataLoader} ) => { const r = await getRethink() - - const existingIds = (await r.table('Organization').getAll(r.args(orgIds))('id').run()) as string[] + const existingOrgs = (await dataLoader.get('organizations').loadMany(orgIds)).filter(isValid) + const existingIds = existingOrgs.map(({id}) => id) const nonExistingIds = orgIds.filter((x) => !existingIds.includes(x)) @@ -17,6 +21,17 @@ const updateOrgFeatureFlag: MutationResolvers['updateOrgFeatureFlag'] = async ( } // RESOLUTION + await getKysely() + .updateTable('Organization') + .$if(addFlag, (db) => db.set({featureFlags: sql`arr_append_uniq("featureFlags",${flag})`})) + .$if(!addFlag, (db) => + db.set({ + featureFlags: sql`ARRAY_REMOVE("featureFlags",${flag})` + }) + ) + .where('id', 'in', orgIds) + .returning('id') + .execute() const updatedOrgIds = (await r .table('Organization') .getAll(r.args(orgIds)) diff --git a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts index e7fd466a0ad..aef8652f492 100644 --- a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts +++ b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts @@ -2,6 +2,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import removeTeamsLimitObjects from '../../../billing/helpers/removeTeamsLimitObjects' import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' +import {toCreditCard} from '../../../postgres/helpers/toCreditCard' import {analytics} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -66,22 +67,34 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( } // RESOLUTION + const creditCard = await getCCFromCustomer(customer) await Promise.all([ + pg + .updateTable('Organization') + .set({ + creditCard: toCreditCard(creditCard), + tier: 'team', + tierLimitExceededAt: null, + scheduledLockAt: null, + lockedAt: null, + trialStartDate: null, + stripeId, + stripeSubscriptionId + }) + .where('id', '=', orgId) + .execute(), r({ - updatedOrg: r - .table('Organization') - .get(orgId) - .update({ - creditCard: await getCCFromCustomer(customer), - tier: 'team', - tierLimitExceededAt: null, - scheduledLockAt: null, - lockedAt: null, - updatedAt: now, - trialStartDate: null, - stripeId, - stripeSubscriptionId - }) + updatedOrg: r.table('Organization').get(orgId).update({ + creditCard, + tier: 'team', + tierLimitExceededAt: null, + scheduledLockAt: null, + lockedAt: null, + updatedAt: now, + trialStartDate: null, + stripeId, + stripeSubscriptionId + }) }).run(), pg .updateTable('Team') diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql index 406fd1d39c0..7530f49fefe 100644 --- a/packages/server/graphql/private/typeDefs/_legacy.graphql +++ b/packages/server/graphql/private/typeDefs/_legacy.graphql @@ -450,7 +450,7 @@ type Mutation { """ When stripe tells us a subscription was updated, update the details in our own DB """ - stripeUpdateSubscription( + stripeCreateSubscription( """ The stripe customer ID, or stripeId """ @@ -1122,7 +1122,7 @@ type JiraServerIssue implements TaskIntegration { The description converted into raw HTML """ descriptionHTML: String! - + """ The timestamp the issue was last updated """ diff --git a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts index db1ea25044e..324c4404ecd 100644 --- a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts @@ -56,11 +56,10 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] // Provided request domain should match team's organizations activeDomain const leadTeams = await getTeamsByIds(validTeamMembers.map((teamMember) => teamMember.teamId)) - const validOrgIds = await r - .table('Organization') - .getAll(r.args(leadTeams.map((team) => team.orgId))) - .filter({activeDomain: domain})('id') - .run() + const teamOrgs = await Promise.all( + leadTeams.map((t) => dataLoader.get('organizations').load(t.orgId)) + ) + const validOrgIds = teamOrgs.filter((org) => org.activeDomain === domain).map(({id}) => id) if (!validOrgIds.length) { return standardError(new Error('Invalid organizations')) diff --git a/packages/server/graphql/public/mutations/createStripeSubscription.ts b/packages/server/graphql/public/mutations/createStripeSubscription.ts index 671078946aa..bebad7a017c 100644 --- a/packages/server/graphql/public/mutations/createStripeSubscription.ts +++ b/packages/server/graphql/public/mutations/createStripeSubscription.ts @@ -45,7 +45,9 @@ const createStripeSubscription: MutationResolvers['createStripeSubscription'] = // cannot updateDefaultPaymentMethod until it is attached to the customer await manager.updateDefaultPaymentMethod(customerId, paymentMethodId) } else { - customer = await manager.createCustomer(orgId, email, paymentMethodId) + const maybeCustomer = await manager.createCustomer(orgId, email, paymentMethodId) + if (maybeCustomer instanceof Error) return {error: {message: maybeCustomer.message}} + customer = maybeCustomer } const subscription = await manager.createTeamSubscription(customer.id, orgId, orgUsersCount) diff --git a/packages/server/graphql/public/mutations/updateCreditCard.ts b/packages/server/graphql/public/mutations/updateCreditCard.ts index d607f16cbe0..984c9758e17 100644 --- a/packages/server/graphql/public/mutations/updateCreditCard.ts +++ b/packages/server/graphql/public/mutations/updateCreditCard.ts @@ -2,6 +2,8 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import Stripe from 'stripe' import removeTeamsLimitObjects from '../../../billing/helpers/removeTeamsLimitObjects' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {toCreditCard} from '../../../postgres/helpers/toCreditCard' import updateTeamByOrgId from '../../../postgres/queries/updateTeamByOrgId' import {getUserId, isUserBillingLeader} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -56,6 +58,18 @@ const updateCreditCard: MutationResolvers['updateCreditCard'] = async ( const creditCard = stripeCardToDBCard(stripeCard) await Promise.all([ + getKysely() + .updateTable('Organization') + .set({ + creditCard: toCreditCard(creditCard), + tier: 'team', + stripeId: customer.id, + tierLimitExceededAt: null, + scheduledLockAt: null, + lockedAt: null + }) + .where('id', '=', orgId) + .execute(), r({ updatedOrg: r.table('Organization').get(orgId).update({ creditCard, diff --git a/packages/server/graphql/public/mutations/updateOrg.ts b/packages/server/graphql/public/mutations/updateOrg.ts index e0be5066410..3aedaf7d82c 100644 --- a/packages/server/graphql/public/mutations/updateOrg.ts +++ b/packages/server/graphql/public/mutations/updateOrg.ts @@ -1,5 +1,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getUserId, isUserBillingLeader} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' @@ -41,6 +42,11 @@ const updateOrg: MutationResolvers['updateOrg'] = async ( name: normalizedName, updatedAt: now } + await getKysely() + .updateTable('Organization') + .set({name: normalizedName}) + .where('id', '=', orgId) + .execute() await r.table('Organization').get(orgId).update(dbUpdate).run() const data = {orgId} diff --git a/packages/server/graphql/public/mutations/uploadOrgImage.ts b/packages/server/graphql/public/mutations/uploadOrgImage.ts index 22f4812cf9f..e03a52f2ce7 100644 --- a/packages/server/graphql/public/mutations/uploadOrgImage.ts +++ b/packages/server/graphql/public/mutations/uploadOrgImage.ts @@ -3,6 +3,7 @@ import getRethink from '../../../database/rethinkDriver' import getFileStoreManager from '../../../fileStorage/getFileStoreManager' import normalizeAvatarUpload from '../../../fileStorage/normalizeAvatarUpload' import validateAvatarUpload from '../../../fileStorage/validateAvatarUpload' +import getKysely from '../../../postgres/getKysely' import {getUserId, isUserBillingLeader} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' @@ -33,7 +34,11 @@ const uploadOrgImage: MutationResolvers['uploadOrgImage'] = async ( const [normalExt, normalBuffer] = await normalizeAvatarUpload(validExt, validBuffer) const manager = getFileStoreManager() const publicLocation = await manager.putOrgAvatar(normalBuffer, orgId, normalExt) - + await getKysely() + .updateTable('Organization') + .set({picture: publicLocation}) + .where('id', '=', orgId) + .execute() await r .table('Organization') .get(orgId) diff --git a/packages/server/graphql/public/types/DomainJoinRequest.ts b/packages/server/graphql/public/types/DomainJoinRequest.ts index ffe93556170..0b030ce8d79 100644 --- a/packages/server/graphql/public/types/DomainJoinRequest.ts +++ b/packages/server/graphql/public/types/DomainJoinRequest.ts @@ -31,11 +31,10 @@ const DomainJoinRequest: DomainJoinRequestResolvers = { const leadTeamIds = leadTeamMembers.map((teamMember) => teamMember.teamId) const leadTeams = (await dataLoader.get('teams').loadMany(leadTeamIds)).filter(isValid) - const validOrgIds = await r - .table('Organization') - .getAll(r.args(leadTeams.map((team) => team.orgId))) - .filter({activeDomain: domain})('id') - .run() + const teamOrgs = await Promise.all( + leadTeams.map((t) => dataLoader.get('organizations').load(t.orgId)) + ) + const validOrgIds = teamOrgs.filter((org) => org.activeDomain === domain).map(({id}) => id) const validTeams = leadTeams.filter((team) => validOrgIds.includes(team.orgId)) return validTeams diff --git a/packages/server/graphql/queries/invoices.ts b/packages/server/graphql/queries/invoices.ts index 9454ec627c7..c9e043efd8c 100644 --- a/packages/server/graphql/queries/invoices.ts +++ b/packages/server/graphql/queries/invoices.ts @@ -38,7 +38,7 @@ export default { } // RESOLUTION - const {stripeId} = await r.table('Organization').get(orgId).pluck('stripeId').run() + const {stripeId} = await dataLoader.get('organizations').load(orgId) const dbAfter = after ? new Date(after) : r.maxval const [tooManyInvoices, orgUserCount] = await Promise.all([ r diff --git a/packages/server/package.json b/packages/server/package.json index 8c4d82c61cd..8f51b4b15fd 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.38.0", + "version": "7.38.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.24.1", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.38.0", + "parabol-client": "7.38.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/packages/server/postgres/helpers/toCreditCard.ts b/packages/server/postgres/helpers/toCreditCard.ts new file mode 100644 index 00000000000..2f4e4c02b42 --- /dev/null +++ b/packages/server/postgres/helpers/toCreditCard.ts @@ -0,0 +1,6 @@ +import {sql} from 'kysely' +import CreditCard from '../../database/types/CreditCard' +export const toCreditCard = (creditCard: CreditCard | undefined | null) => { + if (!creditCard) return null + return sql`(select json_populate_record(null::"CreditCard", ${JSON.stringify(creditCard)}))` +} diff --git a/packages/server/postgres/helpers/toGoogleAnalyzedEntity.ts b/packages/server/postgres/helpers/toGoogleAnalyzedEntity.ts new file mode 100644 index 00000000000..7fac73eefbd --- /dev/null +++ b/packages/server/postgres/helpers/toGoogleAnalyzedEntity.ts @@ -0,0 +1,6 @@ +import {sql} from 'kysely' +import GoogleAnalyzedEntity from '../../database/types/GoogleAnalyzedEntity' +export const toGoogleAnalyzedEntity = (entities: GoogleAnalyzedEntity[]) => + sql< + string[] + >`(select coalesce(array_agg((name, salience, lemma)::"GoogleAnalyzedEntity"), '{}') from json_populate_recordset(null::"GoogleAnalyzedEntity", ${JSON.stringify(entities)}))` diff --git a/packages/server/postgres/migrations/1719435764047_Organization-phase1.ts b/packages/server/postgres/migrations/1719435764047_Organization-phase1.ts new file mode 100644 index 00000000000..719d7a6953b --- /dev/null +++ b/packages/server/postgres/migrations/1719435764047_Organization-phase1.ts @@ -0,0 +1,55 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + //activeDomain has a few that are longer than 100 + await client.query(` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'CreditCard') THEN + CREATE TYPE "CreditCard" AS (brand text, expiry text, last4 smallint); + END IF; + + CREATE TABLE IF NOT EXISTS "Organization" ( + "id" VARCHAR(100) PRIMARY KEY, + "activeDomain" VARCHAR(100), + "isActiveDomainTouched" BOOLEAN NOT NULL DEFAULT FALSE, + "creditCard" "CreditCard", + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "name" VARCHAR(100) NOT NULL, + "payLaterClickCount" SMALLINT NOT NULL DEFAULT 0, + "periodEnd" TIMESTAMP WITH TIME ZONE, + "periodStart" TIMESTAMP WITH TIME ZONE, + "picture" VARCHAR(2056), + "showConversionModal" BOOLEAN NOT NULL DEFAULT FALSE, + "stripeId" VARCHAR(100), + "stripeSubscriptionId" VARCHAR(100), + "upcomingInvoiceEmailSentAt" TIMESTAMP WITH TIME ZONE, + "tier" "TierEnum" NOT NULL DEFAULT 'starter', + "tierLimitExceededAt" TIMESTAMP WITH TIME ZONE, + "trialStartDate" TIMESTAMP WITH TIME ZONE, + "scheduledLockAt" TIMESTAMP WITH TIME ZONE, + "lockedAt" TIMESTAMP WITH TIME ZONE, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "featureFlags" TEXT[] NOT NULL DEFAULT '{}' + ); + CREATE INDEX IF NOT EXISTS "idx_Organization_activeDomain" ON "Organization"("activeDomain"); + CREATE INDEX IF NOT EXISTS "idx_Organization_tier" ON "Organization"("tier"); + DROP TRIGGER IF EXISTS "update_Organization_updatedAt" ON "Organization"; + CREATE TRIGGER "update_Organization_updatedAt" BEFORE UPDATE ON "Organization" FOR EACH ROW EXECUTE PROCEDURE "set_updatedAt"(); + END $$; +`) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE "Organization"; + DROP TYPE "CreditCard"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts index e4eba005f50..e53f082ffce 100644 --- a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts +++ b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts @@ -1,17 +1,21 @@ import getRethink from '../database/rethinkDriver' +import {DataLoaderInstance} from '../dataloader/RootDataLoader' import getTeamsByOrgIds from '../postgres/queries/getTeamsByOrgIds' // Only does something if the organization is empty & not paid // safeArchiveTeam & downgradeToStarter should be called before calling this -const safeArchiveEmptyStarterOrganization = async (orgId: string) => { +const safeArchiveEmptyStarterOrganization = async ( + orgId: string, + dataLoader: DataLoaderInstance +) => { const r = await getRethink() const now = new Date() const orgTeams = await getTeamsByOrgIds([orgId]) const teamCountRemainingOnOldOrg = orgTeams.length if (teamCountRemainingOnOldOrg > 0) return - const org = await r.table('Organization').get(orgId).run() + const org = await dataLoader.get('organizations').load(orgId) if (org.tier !== 'starter') return await r diff --git a/packages/server/socketHandlers/handleOpen.ts b/packages/server/socketHandlers/handleOpen.ts index 151ac82673f..a2e0f5fc77e 100644 --- a/packages/server/socketHandlers/handleOpen.ts +++ b/packages/server/socketHandlers/handleOpen.ts @@ -32,6 +32,15 @@ const handleOpen: WebSocketBehavior['open'] = async (socket) => return } + // add the connectionContext before an async call to make sure it's available in handleMessage + const connectionContext = (socket.getUserData().connectionContext = new ConnectionContext( + socket, + authToken, + ip + )) + + activeClients.set(connectionContext) + // ALL async calls must come after the message listener, or we'll skip out on messages (e.g. resub after server restart) const {sub: userId, iat} = authToken const isBlacklistedJWT = await checkBlacklistJWT(userId, iat) @@ -40,12 +49,6 @@ const handleOpen: WebSocketBehavior['open'] = async (socket) => return } - const connectionContext = (socket.getUserData().connectionContext = new ConnectionContext( - socket, - authToken, - ip - )) - activeClients.set(connectionContext) // messages will start coming in before handleConnect completes & sit in the readyQueue const nextAuthToken = await handleConnect(connectionContext) sendEncodedMessage(connectionContext, {version: APP_VERSION, authToken: nextAuthToken}) diff --git a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts index 6858d2854cf..1a152cfa456 100644 --- a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts +++ b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts @@ -2,9 +2,11 @@ import {r} from 'rethinkdb-ts' import getRethinkConfig from '../../database/getRethinkConfig' import getRethink from '../../database/rethinkDriver' +import {TierEnum} from '../../database/types/Invoice' import OrganizationUser from '../../database/types/OrganizationUser' import generateUID from '../../generateUID' import {DataLoaderWorker} from '../../graphql/graphql' +import getKysely from '../../postgres/getKysely' import getRedis from '../getRedis' import {getEligibleOrgIdsByDomain} from '../isRequestToJoinDomainAllowed' jest.mock('../../database/rethinkDriver') @@ -44,7 +46,7 @@ type TestOrganizationUser = Partial< const addOrg = async ( activeDomain: string | null, members: TestOrganizationUser[], - rest?: {featureFlags?: string[]; tier?: string} + rest?: {featureFlags?: string[]; tier?: TierEnum} ) => { const {featureFlags, tier} = rest ?? {} const orgId = generateUID() @@ -52,6 +54,7 @@ const addOrg = async ( id: orgId, activeDomain, featureFlags, + name: 'foog', tier: tier ?? 'starter' } @@ -63,7 +66,7 @@ const addOrg = async ( role: member.role ?? null, removedAt: member.removedAt ?? null })) - + await getKysely().insertInto('Organization').values(org).execute() await r.table('Organization').insert(org).run() await r.table('OrganizationUser').insert(orgUsers).run() return orgId diff --git a/packages/server/utils/stripe/StripeManager.ts b/packages/server/utils/stripe/StripeManager.ts index 346d5456776..06594ed064a 100644 --- a/packages/server/utils/stripe/StripeManager.ts +++ b/packages/server/utils/stripe/StripeManager.ts @@ -96,19 +96,23 @@ export default class StripeManager { paymentMethodId?: string | undefined, source?: string ) { - return this.stripe.customers.create({ - email, - source, - payment_method: paymentMethodId, - invoice_settings: paymentMethodId - ? { - default_payment_method: paymentMethodId - } - : undefined, - metadata: { - orgId - } - }) + try { + return await this.stripe.customers.create({ + email, + source, + payment_method: paymentMethodId, + invoice_settings: paymentMethodId + ? { + default_payment_method: paymentMethodId + } + : undefined, + metadata: { + orgId + } + }) + } catch (e) { + return e as Error + } } async createEnterpriseSubscription( diff --git a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts index 3227fae1507..64fc00b923d 100644 --- a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts +++ b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts @@ -55,6 +55,8 @@ const rewriteIndexHTML = () => { github: process.env.GITHUB_CLIENT_ID, google: process.env.GOOGLE_OAUTH_CLIENT_ID, googleAnalytics: process.env.GA_TRACKING_ID, + mattermostDisabled: process.env.MATTERMOST_DISABLED === 'true', + msTeamsDisabled: process.env.MSTEAMS_DISABLED === 'true', sentry: process.env.SENTRY_DSN, slack: process.env.SLACK_CLIENT_ID, stripe: process.env.STRIPE_PUBLISHABLE_KEY, diff --git a/scripts/toolboxSrc/primeIntegrations.ts b/scripts/toolboxSrc/primeIntegrations.ts index e56a06c18e3..e441c1a59ae 100644 --- a/scripts/toolboxSrc/primeIntegrations.ts +++ b/scripts/toolboxSrc/primeIntegrations.ts @@ -8,7 +8,7 @@ const upsertGlobalIntegrationProvidersFromEnv = async () => { authStrategy: 'oauth2', scope: 'global', teamId: 'aGhostTeam', - serverBaseUrl: 'https://gitlab.com', + serverBaseUrl: process.env.GITLAB_SERVER_URL, clientId: process.env.GITLAB_CLIENT_ID, clientSecret: process.env.GITLAB_CLIENT_SECRET }, @@ -34,7 +34,7 @@ const upsertGlobalIntegrationProvidersFromEnv = async () => { } ] as const - const validProviders = providers.filter(({clientId, clientSecret}) => clientId && clientSecret) + const validProviders = providers.filter(({clientId, clientSecret, serverBaseUrl}) => clientId && clientSecret && serverBaseUrl) await Promise.all( validProviders.map((provider) => { return upsertIntegrationProvider(provider) diff --git a/scripts/toolboxSrc/setIsEnterprise.ts b/scripts/toolboxSrc/setIsEnterprise.ts index 9aab65a8674..4fe84a5242b 100644 --- a/scripts/toolboxSrc/setIsEnterprise.ts +++ b/scripts/toolboxSrc/setIsEnterprise.ts @@ -1,22 +1,25 @@ +import getKysely from 'parabol-server/postgres/getKysely' import getRethink from '../../packages/server/database/rethinkDriver' import getPg from '../../packages/server/postgres/getPg' import {defaultTier} from '../../packages/server/utils/defaultTier' export default async function setIsEnterprise() { if (defaultTier !== 'enterprise') { - throw new Error('Environment variable IS_ENTERPRISE is not set to true. Exiting without updating tiers.') + throw new Error( + 'Environment variable IS_ENTERPRISE is not set to true. Exiting without updating tiers.' + ) } - + const r = await getRethink() console.log( 'Updating tier to "enterprise" for Organization and OrganizationUser tables in RethinkDB' ) - + type RethinkTableKey = 'Organization' | 'OrganizationUser' const tablesToUpdate: RethinkTableKey[] = ['Organization', 'OrganizationUser'] - + await getKysely().updateTable('Organization').set({tier: 'enterprise'}).execute() const rethinkPromises = tablesToUpdate.map(async (table) => { const result = await r .table(table) diff --git a/scripts/webpack/dev.client.config.js b/scripts/webpack/dev.client.config.js index e695115b3e3..5ba6b1dc9da 100644 --- a/scripts/webpack/dev.client.config.js +++ b/scripts/webpack/dev.client.config.js @@ -119,6 +119,8 @@ module.exports = { github: process.env.GITHUB_CLIENT_ID, google: process.env.GOOGLE_OAUTH_CLIENT_ID, googleAnalytics: process.env.GA_TRACKING_ID, + mattermostDisabled: process.env.MATTERMOST_DISABLED === 'true', + msTeamsDisabled: process.env.MSTEAMS_DISABLED === 'true', sentry: process.env.SENTRY_DSN, slack: process.env.SLACK_CLIENT_ID, stripe: process.env.STRIPE_PUBLISHABLE_KEY,